diff --git a/.circleci/config.yml b/.circleci/config.yml
index a4ff9a685..2ef79658b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,17 +1,44 @@
version: 2.1
+executorType: machine
jobs:
- kp-build:
- machine: true
+ build:
+ docker:
+ - image: circleci/openjdk:14-jdk-buster-node-browsers-legacy
steps:
- checkout
- restore_cache:
- key: kp-dependency-cache-{{ checksum "pom.xml" }}
+ key: kp-dependency-build-cache-{{ checksum "pom.xml" }}
- run:
- name: Setup VM and Build
+ name: Run build
+ command: |
+ mvn clean install -DskipTests
+ - save_cache:
+ paths:
+ - ~/.m2
+ key: kp-dependency-build-cache-{{ checksum "pom.xml" }}
+
+ unit-tests:
+ docker:
+ - image: circleci/openjdk:14-jdk-buster-node-browsers-legacy
+ - image: circleci/redis:latest
+ parallelism: 1
+ steps:
+ - checkout
+ - restore_cache:
+ key: kp-dependency-test-cache-{{ checksum "pom.xml" }}
+ - run:
+ name: Setup environment and run tests
command: bash vmsetup.sh
+ - save_cache:
+ paths:
+ - ~/.m2
+ key: kp-dependency-test-cache-{{ checksum "pom.xml" }}
workflows:
version: 2.1
- workflow:
- jobs:
- - kp-build
+ build-then-test:
+ jobs:
+ - build
+ - unit-tests:
+ requires:
+ - build
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index f1826648e..920b32a49 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -18,7 +18,7 @@ Please describe the tests that you ran to verify your changes in the below check
**Test Configuration**:
* Software versions: Java 11, scala-2.11, play-2.7.2
-* Hardware versions:
+* Hardware versions: 2 CPU/ 4GB RAM
### Checklist:
@@ -30,3 +30,4 @@ Please describe the tests that you ran to verify your changes in the below check
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
+
diff --git a/.github/pull_request_template.md.yaml b/.github/pull_request_template.md.yaml
new file mode 100644
index 000000000..f1826648e
--- /dev/null
+++ b/.github/pull_request_template.md.yaml
@@ -0,0 +1,32 @@
+Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
+
+### Type of change
+
+Please choose appropriate options.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+
+### How Has This Been Tested?
+
+Please describe the tests that you ran to verify your changes in the below checkboxes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
+
+- [ ] Ran Test A
+- [ ] Ran Test B
+
+**Test Configuration**:
+* Software versions: Java 11, scala-2.11, play-2.7.2
+* Hardware versions:
+
+### Checklist:
+
+- [ ] My code follows the style guidelines of this project
+- [ ] I have performed a self-review of my own code
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] My changes generate no new warnings
+- [ ] I have added tests that prove my fix is effective or that my feature works
+- [ ] New and existing unit tests pass locally with my changes
+- [ ] Any dependent changes have been merged and published in downstream modules
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index b31b7eb74..000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM openjdk:8-jre-alpine
-RUN apk update \
- && apk add unzip \
- && apk add curl \
- && adduser -u 1001 -h /home/sunbird/ -D sunbird \
- && mkdir -p /home/sunbird
-RUN chown -R sunbird:sunbird /home/sunbird
-USER sunbird
-COPY ./learning-api/content-service/target/content-service-1.0-SNAPSHOT-dist.zip /home/sunbird/
-RUN unzip /home/sunbird/content-service-1.0-SNAPSHOT-dist.zip -d /home/sunbird/
-RUN rm /home/sunbird/content-service-1.0-SNAPSHOT-dist.zip
-COPY --chown=sunbird ./schemas /home/sunbird/content-service-1.0-SNAPSHOT/schemas
-WORKDIR /home/sunbird/
-CMD java -cp '/home/sunbird/content-service-1.0-SNAPSHOT/lib/*' -Dconfig.file=/home/sunbird/content-service-1.0-SNAPSHOT/config/application.conf play.core.server.ProdServerStart /home/sunbird/content-service-1.0-SNAPSHOT
diff --git a/assessment-api/assessment-actors/pom.xml b/assessment-api/assessment-actors/pom.xml
new file mode 100644
index 000000000..d56dd0a67
--- /dev/null
+++ b/assessment-api/assessment-actors/pom.xml
@@ -0,0 +1,123 @@
+
+
+
+ assessment-api
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ assessment-actors
+
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+ org.sunbird
+ actor-core
+ 1.0-SNAPSHOT
+
+
+ org.sunbird
+ graph-engine_2.11
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.sunbird
+ qs-hierarchy-manager
+ 1.0-SNAPSHOT
+
+
+ org.sunbird
+ import-manager
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ ${scalatest.version}
+ test
+
+
+ org.scalamock
+ scalamock_${scala.maj.version}
+ 4.4.0
+ test
+
+
+ com.typesafe.akka
+ akka-testkit_${scala.maj.version}
+ 2.5.22
+ test
+
+
+
+
+ src/main/scala
+ src/test/scala
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.4.0
+
+ ${scala.version}
+ false
+
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+ scala-test-compile
+ process-test-resources
+
+ testCompile
+
+
+
+
+
+ org.scalatest
+ scalatest-maven-plugin
+ 2.0.0
+
+
+ test
+ test
+
+ test
+
+
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/HealthActor.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/HealthActor.scala
new file mode 100644
index 000000000..3072bb928
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/HealthActor.scala
@@ -0,0 +1,20 @@
+package org.sunbird.actors
+
+import javax.inject.Inject
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.health.HealthCheckManager
+
+import scala.concurrent.{ExecutionContext, Future}
+
+
+class HealthActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ @throws[Throwable]
+ override def onReceive(request: Request): Future[Response] = {
+ HealthCheckManager.checkAllSystemHealth()
+ }
+}
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/ItemSetActor.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/ItemSetActor.scala
new file mode 100644
index 000000000..524a0604c
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/ItemSetActor.scala
@@ -0,0 +1,98 @@
+package org.sunbird.actors
+
+import java.util
+
+import javax.inject.Inject
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Relation
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.parseq.Task
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters.seqAsJavaListConverter
+import scala.concurrent.{ExecutionContext, Future}
+
+class ItemSetActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = request.getOperation match {
+ case "createItemSet" => create(request)
+ case "readItemSet" => read(request)
+ case "updateItemSet" => update(request)
+ case "reviewItemSet" => review(request)
+ case "retireItemSet" => retire(request)
+ case _ => ERROR(request.getOperation)
+ }
+
+
+ def create(request: Request): Future[Response] = DataNode.create(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ })
+
+ def read(request: Request): Future[Response] = {
+ val fields = request.getRequest.getOrDefault("fields", "").asInstanceOf[String]
+ .split(",").filter((field: String) => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null")).toList.asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ val metadata = NodeUtil.serialize(node, fields, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ metadata.remove("versionKey")
+ ResponseHandler.OK.put("itemset", metadata)
+ })
+ }
+
+ def update(request: Request): Future[Response] = DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ })
+
+ def review(request: Request): Future[Response] = {
+ val identifier = request.getContext.get("identifier").asInstanceOf[String]
+ var flag = false
+ val readReq = new Request();
+ val reqContext = request.getContext
+ readReq.setContext(reqContext)
+ readReq.put("identifier", identifier)
+ readReq.put("fields", new util.ArrayList[String])
+ val updateReq = new Request()
+ updateReq.setContext(reqContext)
+ DataNode.read(readReq).map(node => {
+ if (CollectionUtils.isNotEmpty(node.getOutRelations)) {
+ //process relations with AssessmentItem
+ val itemRels: util.List[Relation] = node.getOutRelations.filter((rel: Relation) => StringUtils.equalsAnyIgnoreCase("AssessmentItem", rel.getEndNodeObjectType)).filterNot((reln: Relation) => StringUtils.equalsAnyIgnoreCase("Retired", reln.getEndNodeMetadata.get("status").toString))
+ val draftRelIds: List[String] = itemRels.filter((rel: Relation) => StringUtils.equalsAnyIgnoreCase("Draft", rel.getEndNodeMetadata.get("status").toString)).map(rel => rel.getEndNodeId).toList
+ if (CollectionUtils.isNotEmpty(draftRelIds)) {
+ updateReq.put("identifiers", draftRelIds.asJava)
+ updateReq.put("metadata", new util.HashMap[String, AnyRef]() {{put("status", "Review")}})
+ flag = true
+ }
+ val newRels: util.List[util.HashMap[String, AnyRef]] = itemRels.sortBy((rel: Relation) => rel.getMetadata.get("IL_SEQUENCE_INDEX").asInstanceOf[Long])(Ordering.Long).map(rel => {
+ new util.HashMap[String, AnyRef]() {{put("identifier", rel.getEndNodeId);}}}).toList
+ request.put("items", newRels);
+ }
+ request.put("status", "Review")
+ val func = flag match {
+ case true => DataNode.bulkUpdate(updateReq).map(f => ResponseHandler.OK())
+ case false => Future(ResponseHandler.OK())
+ }
+ val futureList = Task.parallel[Response](func,
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ }))
+ futureList
+ }).flatMap(f => f).map(f => f.get(1))
+ }
+
+ def retire(request: Request): Future[Response] = {
+ request.put("status", "Retired")
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ })
+ }
+
+
+}
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionActor.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionActor.scala
new file mode 100644
index 000000000..478060d2e
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionActor.scala
@@ -0,0 +1,115 @@
+package org.sunbird.actors
+
+import org.sunbird.`object`.importer.{ImportConfig, ImportManager}
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.{DateUtils, Platform}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.managers.AssessmentManager
+import org.sunbird.utils.RequestUtil
+import java.util
+
+import javax.inject.Inject
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.graph.utils.NodeUtil
+
+import scala.collection.JavaConverters
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+class QuestionActor @Inject()(implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ private lazy val importConfig = getImportConfig()
+ private lazy val importMgr = new ImportManager(importConfig)
+
+ override def onReceive(request: Request): Future[Response] = request.getOperation match {
+ case "createQuestion" => AssessmentManager.create(request, "ERR_QUESTION_CREATE")
+ case "readQuestion" => AssessmentManager.read(request, "question")
+ case "updateQuestion" => update(request)
+ case "reviewQuestion" => review(request)
+ case "publishQuestion" => publish(request)
+ case "retireQuestion" => retire(request)
+ case "importQuestion" => importQuestion(request)
+ case "systemUpdateQuestion" => systemUpdate(request)
+ case "listQuestions" => listQuestions(request)
+ case _ => ERROR(request.getOperation)
+ }
+
+ def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForUpdate(request, "ERR_QUESTION_UPDATE").flatMap(_ => AssessmentManager.updateNode(request))
+ }
+
+ def review(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForReview(request, "ERR_QUESTION_REVIEW").flatMap(node => {
+ val updateRequest = new Request(request)
+ updateRequest.getContext.put("identifier", request.get("identifier"))
+ updateRequest.putAll(Map("versionKey" -> node.getMetadata.get("versionKey"), "prevState" -> "Draft", "status" -> "Review", "lastStatusChangedOn" -> DateUtils.formatCurrentDate).asJava)
+ AssessmentManager.updateNode(updateRequest)
+ })
+ }
+
+ def publish(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForPublish(request, "ERR_QUESTION_PUBLISH").map(node => {
+ AssessmentManager.pushInstructionEvent(node.getIdentifier, node)
+ ResponseHandler.OK.putAll(Map[String, AnyRef]("identifier" -> node.getIdentifier.replace(".img", ""), "message" -> "Question is successfully sent for Publish").asJava)
+ })
+ }
+
+ def retire(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForRetire(request, "ERR_QUESTION_RETIRE").flatMap(node => {
+ val updateRequest = new Request(request)
+ updateRequest.put("identifiers", java.util.Arrays.asList(request.get("identifier").asInstanceOf[String], request.get("identifier").asInstanceOf[String] + ".img"))
+ val updateMetadata: util.Map[String, AnyRef] = Map[String, AnyRef]("status" -> "Retired", "lastStatusChangedOn" -> DateUtils.formatCurrentDate).asJava
+ updateRequest.put("metadata", updateMetadata)
+ DataNode.bulkUpdate(updateRequest).map(_ => {
+ ResponseHandler.OK.putAll(Map("identifier" -> node.getIdentifier.replace(".img", ""), "versionKey" -> node.getMetadata.get("versionKey")).asJava)
+ })
+ })
+ }
+
+ def importQuestion(request: Request): Future[Response] = importMgr.importObject(request)
+
+ def getImportConfig(): ImportConfig = {
+ val requiredProps = Platform.getStringList("import.required_props.question", java.util.Arrays.asList("name", "code", "mimeType", "framework")).asScala.toList
+ val validStages = Platform.getStringList("import.valid_stages.question", java.util.Arrays.asList("create", "upload", "review", "publish")).asScala.toList
+ val propsToRemove = Platform.getStringList("import.remove_props.question", java.util.Arrays.asList()).asScala.toList
+ val topicName = Platform.config.getString("import.output_topic_name")
+ val reqLimit = Platform.getInteger("import.request_size_limit", 200)
+ ImportConfig(topicName, reqLimit, requiredProps, validStages, propsToRemove)
+ }
+
+ def systemUpdate(request: Request): Future[Response] = {
+ val identifier = request.getContext.get("identifier").asInstanceOf[String]
+ RequestUtil.validateRequest(request)
+ val readReq = new Request(request)
+ val identifiers = new util.ArrayList[String](){{
+ add(identifier)
+ if (!identifier.endsWith(".img"))
+ add(identifier.concat(".img"))
+ }}
+ readReq.put("identifiers", identifiers)
+ DataNode.list(readReq).flatMap(response => {
+ DataNode.systemUpdate(request, response,"", None)
+ }).map(node => ResponseHandler.OK.put("identifier", identifier).put("status", "success"))
+ }
+
+ def listQuestions(request: Request): Future[Response] = {
+ RequestUtil.validateListRequest(request)
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.search(request).map(nodeList => {
+ val questionList = nodeList.map(node => {
+ NodeUtil.serialize(node, fields, node.getObjectType.toLowerCase.replace("Image", ""), request.getContext.get("version").asInstanceOf[String])
+ }).asJava
+ ResponseHandler.OK.put("questions", questionList).put("count", questionList.size)
+ })
+ }
+}
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala
new file mode 100644
index 000000000..b48d8e64e
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala
@@ -0,0 +1,155 @@
+package org.sunbird.actors
+
+import java.util
+
+import javax.inject.Inject
+import org.apache.commons.collections4.CollectionUtils
+import org.sunbird.`object`.importer.{ImportConfig, ImportManager}
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.{DateUtils, Platform}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.managers.HierarchyManager.hierarchyPrefix
+import org.sunbird.managers.{AssessmentManager, HierarchyManager, UpdateHierarchyManager}
+import org.sunbird.utils.RequestUtil
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+class QuestionSetActor @Inject()(implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+ private lazy val importConfig = getImportConfig()
+ private lazy val importMgr = new ImportManager(importConfig)
+
+ override def onReceive(request: Request): Future[Response] = request.getOperation match {
+ case "createQuestionSet" => AssessmentManager.create(request, "ERR_QUESTION_SET_CREATE")
+ case "readQuestionSet" => AssessmentManager.read(request, "questionset")
+ case "updateQuestionSet" => update(request)
+ case "reviewQuestionSet" => review(request)
+ case "publishQuestionSet" => publish(request)
+ case "retireQuestionSet" => retire(request)
+ case "addQuestion" => HierarchyManager.addLeafNodesToHierarchy(request)
+ case "removeQuestion" => HierarchyManager.removeLeafNodesFromHierarchy(request)
+ case "updateHierarchy" => UpdateHierarchyManager.updateHierarchy(request)
+ case "getHierarchy" => HierarchyManager.getHierarchy(request)
+ case "rejectQuestionSet" => reject(request)
+ case "importQuestionSet" => importQuestionSet(request)
+ case "systemUpdateQuestionSet" => systemUpdate(request)
+ case _ => ERROR(request.getOperation)
+ }
+
+ def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForUpdate(request, "ERR_QUESTION_SET_UPDATE").flatMap(_ => AssessmentManager.updateNode(request))
+ }
+
+ def review(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ request.getRequest.put("mode", "edit")
+ AssessmentManager.getValidatedNodeForReview(request, "ERR_QUESTION_SET_REVIEW").flatMap(node => {
+ AssessmentManager.getQuestionSetHierarchy(request, node).flatMap(hierarchyString => {
+ AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String])
+ val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Review")
+ val updateReq = new Request(request)
+ val date = DateUtils.formatCurrentDate
+ updateReq.putAll(Map("identifiers" -> nodeIds, "metadata" -> Map("status" -> "Review", "prevState" -> node.getMetadata.get("status"), "lastStatusChangedOn" -> date, "lastUpdatedOn" -> date).asJava).asJava)
+ updateHierarchyNodes(updateReq, node, Map("status" -> "Review", "hierarchy" -> updatedHierarchy), nodeIds)
+ })
+ })
+ }
+
+ def publish(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForPublish(request, "ERR_QUESTION_SET_PUBLISH").flatMap(node => {
+ AssessmentManager.getQuestionSetHierarchy(request, node).map(hierarchyString => {
+ AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String])
+ AssessmentManager.pushInstructionEvent(node.getIdentifier, node)
+ ResponseHandler.OK.putAll(Map[String, AnyRef]("identifier" -> node.getIdentifier.replace(".img", ""), "message" -> "Question is successfully sent for Publish").asJava)
+ })
+ })
+ }
+
+ def retire(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ AssessmentManager.getValidatedNodeForRetire(request, "ERR_QUESTION_SET_RETIRE").flatMap(node => {
+ val updateRequest = new Request(request)
+ updateRequest.put("identifiers", java.util.Arrays.asList(request.get("identifier").asInstanceOf[String], request.get("identifier").asInstanceOf[String] + ".img"))
+ val updateMetadata: util.Map[String, AnyRef] = Map("prevState" -> node.getMetadata.get("status"), "status" -> "Retired", "lastStatusChangedOn" -> DateUtils.formatCurrentDate, "lastUpdatedOn" -> DateUtils.formatCurrentDate).asJava
+ updateRequest.put("metadata", updateMetadata)
+ DataNode.bulkUpdate(updateRequest).map(_ => {
+ ResponseHandler.OK.putAll(Map("identifier" -> node.getIdentifier.replace(".img", ""), "versionKey" -> node.getMetadata.get("versionKey")).asJava)
+ })
+ })
+ }
+
+ def reject(request: Request): Future[Response] = {
+ request.getRequest.put("identifier", request.getContext.get("identifier"))
+ request.getRequest.put("mode", "edit")
+ AssessmentManager.getValidateNodeForReject(request, "ERR_QUESTION_SET_REJECT").flatMap(node => {
+ AssessmentManager.getQuestionSetHierarchy(request, node).flatMap(hierarchyString => {
+ AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String])
+ val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Draft")
+ val updateReq = new Request(request)
+ val date = DateUtils.formatCurrentDate
+ updateReq.putAll(Map("identifiers" -> nodeIds, "metadata" -> Map("status" -> "Draft", "prevState" -> node.getMetadata.get("status"), "lastStatusChangedOn" -> date, "lastUpdatedOn" -> date).asJava).asJava)
+ updateHierarchyNodes(updateReq, node, Map("status" -> "Draft", "hierarchy" -> updatedHierarchy), nodeIds)
+ })
+ })
+ }
+
+ def updateHierarchyNodes(request: Request, node: Node, metadata: Map[String, AnyRef], nodeIds: util.List[String]): Future[Response] = {
+ if (CollectionUtils.isNotEmpty(nodeIds)) {
+ DataNode.bulkUpdate(request).flatMap(_ => {
+ updateNode(request, node, metadata)
+ })
+ } else {
+ updateNode(request, node, metadata)
+ }
+ }
+
+ def updateNode(request: Request, node: Node, metadata: Map[String, AnyRef]): Future[Response] = {
+ val updateRequest = new Request(request)
+ val date = DateUtils.formatCurrentDate
+ val fMeta: Map[String, AnyRef] = Map("versionKey" -> node.getMetadata.get("versionKey"), "prevState" -> node.getMetadata.get("status"), "lastStatusChangedOn" -> date, "lastUpdatedOn" -> date) ++ metadata
+ updateRequest.getContext.put("identifier", request.getContext.get("identifier"))
+ updateRequest.putAll(fMeta.asJava)
+ DataNode.update(updateRequest).map(_ => {
+ ResponseHandler.OK.putAll(Map("identifier" -> node.getIdentifier.replace(".img", ""), "versionKey" -> node.getMetadata.get("versionKey")).asJava)
+ })
+ }
+
+ def importQuestionSet(request: Request): Future[Response] = importMgr.importObject(request)
+
+ def getImportConfig(): ImportConfig = {
+ val requiredProps = Platform.getStringList("import.required_props.questionset", java.util.Arrays.asList("name", "code", "mimeType", "framework")).asScala.toList
+ val validStages = Platform.getStringList("import.valid_stages.questionset", java.util.Arrays.asList("create", "upload", "review", "publish")).asScala.toList
+ val propsToRemove = Platform.getStringList("import.remove_props.questionset", java.util.Arrays.asList()).asScala.toList
+ val topicName = Platform.config.getString("import.output_topic_name")
+ val reqLimit = Platform.getInteger("import.request_size_limit", 200)
+ ImportConfig(topicName, reqLimit, requiredProps, validStages, propsToRemove)
+ }
+
+ def systemUpdate(request: Request): Future[Response] = {
+ val identifier = request.getContext.get("identifier").asInstanceOf[String]
+ RequestUtil.validateRequest(request)
+ if(Platform.getBoolean("questionset.cache.enable", false))
+ RedisCache.delete(hierarchyPrefix + identifier)
+
+ val readReq = new Request(request)
+ val identifiers = new util.ArrayList[String](){{
+ add(identifier)
+ if (!identifier.endsWith(".img"))
+ add(identifier.concat(".img"))
+ }}
+ readReq.put("identifiers", identifiers)
+ DataNode.list(readReq).flatMap(response => {
+ DataNode.systemUpdate(request, response,"questionSet", Some(HierarchyManager.getHierarchy))
+ }).map(node => ResponseHandler.OK.put("identifier", identifier).put("status", "success"))
+ }
+
+}
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala
new file mode 100644
index 000000000..97386a152
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala
@@ -0,0 +1,200 @@
+package org.sunbird.managers
+
+import java.util
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.{DateUtils, JsonUtils, Platform}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException, ServerException}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.{Node, Relation}
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.telemetry.util.LogTelemetryEventUtil
+
+import scala.concurrent.{ExecutionContext, Future}
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters
+import scala.collection.JavaConverters._
+
+object AssessmentManager {
+
+ val skipValidation: Boolean = Platform.getBoolean("assessment.skip.validation", true)
+
+ def create(request: Request, errCode: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val visibility: String = request.getRequest.getOrDefault("visibility", "").asInstanceOf[String]
+ if (StringUtils.isNotBlank(visibility) && StringUtils.equalsIgnoreCase(visibility, "Parent"))
+ throw new ClientException(errCode, "Visibility cannot be Parent!")
+ DataNode.create(request).map(node => {
+ val response = ResponseHandler.OK
+ response.putAll(Map("identifier" -> node.getIdentifier, "versionKey" -> node.getMetadata.get("versionKey")).asJava)
+ response
+ })
+ }
+
+ def read(request: Request, resName: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, node.getObjectType.toLowerCase.replace("Image", ""), request.getContext.get("version").asInstanceOf[String])
+ metadata.put("identifier", node.getIdentifier.replace(".img", ""))
+ ResponseHandler.OK.put(resName, metadata)
+ })
+ }
+
+ def updateNode(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.putAll(Map("identifier" -> node.getIdentifier.replace(".img", ""), "versionKey" -> node.getMetadata.get("versionKey")).asJava)
+ })
+ }
+
+ def getValidatedNodeForUpdate(request: Request, errCode: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase(node.getMetadata.getOrDefault("visibility", "").asInstanceOf[String], "Parent"))
+ throw new ClientException(errCode, node.getMetadata.getOrDefault("objectType", "").asInstanceOf[String].replace("Image", "") + " with visibility Parent, can't be updated individually.")
+ node
+ })
+ }
+
+ def getValidatedNodeForReview(request: Request, errCode: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ request.put("mode", "edit")
+ DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase(node.getMetadata.getOrDefault("visibility", "").asInstanceOf[String], "Parent"))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} with visibility Parent, can't be sent for review individually.")
+ if (!StringUtils.equalsAnyIgnoreCase(node.getMetadata.getOrDefault("status", "").asInstanceOf[String], "Draft"))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} with status other than Draft can't be sent for review.")
+ node
+ })
+ }
+
+ def getValidatedNodeForPublish(request: Request, errCode: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ request.put("mode", "edit")
+ DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase(node.getMetadata.getOrDefault("visibility", "").asInstanceOf[String], "Parent"))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} with visibility Parent, can't be sent for publish individually.")
+ if (StringUtils.equalsAnyIgnoreCase(node.getMetadata.getOrDefault("status", "").asInstanceOf[String], "Processing"))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} having Processing status can't be sent for publish.")
+ node
+ })
+ }
+
+ def getValidatedNodeForRetire(request: Request, errCode: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase("Retired", node.getMetadata.get("status").asInstanceOf[String]))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} with identifier : ${node.getIdentifier} is already Retired.")
+ node
+ })
+ }
+
+ def getValidateNodeForReject(request: Request, errCode: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ request.put("mode", "edit")
+ DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase(node.getMetadata.getOrDefault("visibility", "").asInstanceOf[String], "Parent"))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} with visibility Parent, can't be sent for reject individually.")
+ if (!StringUtils.equalsIgnoreCase("Review", node.getMetadata.get("status").asInstanceOf[String]))
+ throw new ClientException(errCode, s"${node.getObjectType.replace("Image", "")} is not in 'Review' state for identifier: " + node.getIdentifier)
+ node
+ })
+ }
+
+ def getValidatedQuestionSet(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ request.put("mode", "edit")
+ DataNode.read(request).map(node => {
+ if (!StringUtils.equalsIgnoreCase("QuestionSet", node.getObjectType))
+ throw new ClientException("ERR_QUESTION_SET_ADD", "Node with Identifier " + node.getIdentifier + " is not a Question Set")
+ node
+ })
+ }
+
+ def validateQuestionSetHierarchy(hierarchyString: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Unit = {
+ if (!skipValidation) {
+ val hierarchy = if (!hierarchyString.asInstanceOf[String].isEmpty) {
+ JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.Map[String, AnyRef]])
+ } else
+ new java.util.HashMap[String, AnyRef]()
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.List[java.util.Map[String, AnyRef]]]
+ validateChildrenRecursive(children)
+ }
+ }
+
+ def getQuestionSetHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Any] = {
+ oec.graphService.readExternalProps(request, List("hierarchy")).flatMap(response => {
+ if (ResponseHandler.checkError(response) && ResponseHandler.isResponseNotFoundError(response)) {
+ if (StringUtils.equalsIgnoreCase("Live", rootNode.getMetadata.get("status").asInstanceOf[String]))
+ throw new ServerException("ERR_QUESTION_SET_REVIEW", "No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier)
+ request.put("identifier", if (!rootNode.getIdentifier.endsWith(".img")) rootNode.getIdentifier + ".img" else rootNode.getIdentifier)
+ oec.graphService.readExternalProps(request, List("hierarchy")).map(resp => {
+ resp.getResult.toMap.getOrElse("hierarchy", "{}").asInstanceOf[String]
+ }) recover { case e: ResourceNotFoundException => TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + request.get("identifier")) }
+ } else Future(response.getResult.toMap.getOrElse("hierarchy", "{}").asInstanceOf[String])
+ })
+ }
+
+ private def validateChildrenRecursive(children: util.List[util.Map[String, AnyRef]]): Unit = {
+ children.toList.foreach(content => {
+ if (!StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Parent")
+ && !StringUtils.equalsIgnoreCase(content.getOrDefault("status", "").asInstanceOf[String], "Live"))
+ throw new ClientException("ERR_QUESTION_SET", "Content with identifier: " + content.get("identifier") + "is not Live. Please Publish it.")
+ validateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]])
+ })
+ }
+
+ def getChildIdsFromRelation(node: Node): (List[String], List[String]) = {
+ val outRelations: List[Relation] = if (node.getOutRelations != null) node.getOutRelations.asScala.toList else List[Relation]()
+ val visibilityIdMap: Map[String, List[String]] = outRelations
+ .groupBy(_.getEndNodeMetadata.get("visibility").asInstanceOf[String])
+ .mapValues(_.map(_.getEndNodeId).toList)
+ (visibilityIdMap.getOrDefault("Default", List()), visibilityIdMap.getOrDefault("Parent", List()))
+ }
+
+ def updateHierarchy(hierarchyString: String, status: String): (java.util.Map[String, AnyRef], java.util.List[String]) = {
+ val hierarchy = if (!hierarchyString.asInstanceOf[String].isEmpty) {
+ JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.Map[String, AnyRef]])
+ } else
+ new java.util.HashMap[String, AnyRef]()
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.List[java.util.Map[String, AnyRef]]]
+ hierarchy.put("status", status)
+ val childrenToUpdate: List[String] = updateChildrenRecursive(children, status, List())
+ (hierarchy, childrenToUpdate.asJava)
+ }
+
+ private def updateChildrenRecursive(children: util.List[util.Map[String, AnyRef]], status: String, idList: List[String]): List[String] = {
+ children.toList.flatMap(content => {
+ val updatedIdList: List[String] =
+ if (StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Parent")) {
+ content.put("lastStatusChangedOn", DateUtils.formatCurrentDate)
+ content.put("status", status)
+ content.put("prevState", "Draft")
+ content.put("lastUpdatedOn", DateUtils.formatCurrentDate)
+ content.get("identifier").asInstanceOf[String] :: idList
+ } else idList
+ val list = updateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], status, updatedIdList)
+ list ++ updatedIdList
+ })
+ }
+
+ @throws[Exception]
+ def pushInstructionEvent(identifier: String, node: Node)(implicit oec: OntologyEngineContext): Unit = {
+ val (actor, context, objData, eData) = generateInstructionEventMetadata(identifier.replace(".img", ""), node)
+ val beJobRequestEvent: String = LogTelemetryEventUtil.logInstructionEvent(actor.asJava, context.asJava, objData.asJava, eData)
+ val topic: String = Platform.getString("kafka.topics.instruction", "sunbirddev.learning.job.request")
+ if (StringUtils.isBlank(beJobRequestEvent)) throw new ClientException("BE_JOB_REQUEST_EXCEPTION", "Event is not generated properly.")
+ oec.kafkaClient.send(beJobRequestEvent, topic)
+ }
+
+ def generateInstructionEventMetadata(identifier: String, node: Node): (Map[String, AnyRef], Map[String, AnyRef], Map[String, AnyRef], util.Map[String, AnyRef]) = {
+ val metadata: util.Map[String, AnyRef] = node.getMetadata
+ val publishType = if (StringUtils.equalsIgnoreCase(metadata.getOrDefault("status", "").asInstanceOf[String], "Unlisted")) "unlisted" else "public"
+ val eventMetadata = Map("identifier" -> identifier, "mimeType" -> metadata.getOrDefault("mimeType", ""), "objectType" -> node.getObjectType.replace("Image", ""), "pkgVersion" -> metadata.getOrDefault("pkgVersion", 0.asInstanceOf[AnyRef]), "lastPublishedBy" -> metadata.getOrDefault("lastPublishedBy", ""))
+ val actor = Map("id" -> s"${node.getObjectType.toLowerCase().replace("image", "")}-publish", "type" -> "System".asInstanceOf[AnyRef])
+ val context = Map("channel" -> metadata.getOrDefault("channel", ""), "pdata" -> Map("id" -> "org.sunbird.platform", "ver" -> "1.0").asJava, "env" -> Platform.getString("cloud_storage.env", "dev"))
+ val objData = Map("id" -> identifier, "ver" -> metadata.getOrDefault("versionKey", ""))
+ val eData: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef] {{
+ put("action", "publish")
+ put("publish_type", publishType)
+ put("metadata", eventMetadata.asJava)
+ }}
+ (actor, context, objData, eData)
+ }
+}
diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/utils/RequestUtil.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/utils/RequestUtil.scala
new file mode 100644
index 000000000..4246a74c8
--- /dev/null
+++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/utils/RequestUtil.scala
@@ -0,0 +1,43 @@
+package org.sunbird.utils
+
+import org.sunbird.common.Platform
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.{ClientException, ErrorCodes}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.schema.DefinitionNode
+
+import scala.concurrent.ExecutionContext
+import scala.collection.JavaConversions._
+
+object RequestUtil {
+
+ private val SYSTEM_UPDATE_ALLOWED_CONTENT_STATUS = List("Live", "Unlisted")
+ val questionListLimit = if (Platform.config.hasPath("question.list.limit")) Platform.config.getInt("question.list.limit") else 20
+
+ def restrictProperties(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Unit = {
+ val graphId = request.getContext.getOrDefault("graph_id","").asInstanceOf[String]
+ val version = request.getContext.getOrDefault("version","").asInstanceOf[String]
+ val objectType = request.getContext.getOrDefault("objectType", "").asInstanceOf[String]
+ val schemaName = request.getContext.getOrDefault("schemaName","").asInstanceOf[String]
+ val operation = request.getOperation.toLowerCase.replace(objectType.toLowerCase, "")
+ val restrictedProps =DefinitionNode.getRestrictedProperties(graphId, version, operation, schemaName)
+ if (restrictedProps.exists(prop => request.getRequest.containsKey(prop))) throw new ClientException("ERROR_RESTRICTED_PROP", "Properties in list " + restrictedProps.mkString("[", ", ", "]") + " are not allowed in request")
+ }
+
+ def validateRequest(request: Request): Unit = {
+ if (request.getRequest.isEmpty)
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), s"Request Body cannot be Empty.")
+
+ if (request.get("status") != null && SYSTEM_UPDATE_ALLOWED_CONTENT_STATUS.contains(request.get("status").asInstanceOf[String]))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), s"Cannot update content status to : ${SYSTEM_UPDATE_ALLOWED_CONTENT_STATUS.mkString("[", ", ", "]")}.")
+
+ }
+
+ def validateListRequest(request: Request): Unit = {
+ if (request.get("identifiers") == null || request.get("identifiers").asInstanceOf[java.util.List[String]].isEmpty)
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Required field identifier is missing or empty.")
+
+ if (request.get("identifiers").asInstanceOf[java.util.List[String]].length > questionListLimit)
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Request contains more than the permissible limit of identifier: 20.")
+ }
+}
diff --git a/assessment-api/assessment-actors/src/test/resources/application.conf b/assessment-api/assessment-actors/src/test/resources/application.conf
new file mode 100644
index 000000000..f873cbaf6
--- /dev/null
+++ b/assessment-api/assessment-actors/src/test/resources/application.conf
@@ -0,0 +1,416 @@
+# This is the main configuration file for the application.
+# https://www.playframework.com/documentation/latest/ConfigFile
+# ~~~~~
+# Play uses HOCON as its configuration file format. HOCON has a number
+# of advantages over other config formats, but there are two things that
+# can be used when modifying settings.
+#
+# You can include other configuration files in this main application.conf file:
+#include "extra-config.conf"
+#
+# You can declare variables and substitute for them:
+#mykey = ${some.value}
+#
+# And if an environment variable exists when there is no other substitution, then
+# HOCON will fall back to substituting environment variable:
+#mykey = ${JAVA_HOME}
+
+## Akka
+# https://www.playframework.com/documentation/latest/ScalaAkka#Configuration
+# https://www.playframework.com/documentation/latest/JavaAkka#Configuration
+# ~~~~~
+# Play uses Akka internally and exposes Akka Streams and actors in Websockets and
+# other streaming HTTP responses.
+akka {
+ # "akka.log-config-on-start" is extraordinarly useful because it log the complete
+ # configuration at INFO level, including defaults and overrides, so it s worth
+ # putting at the very top.
+ #
+ # Put the following in your conf/logback.xml file:
+ #
+ #
+ #
+ # And then uncomment this line to debug the configuration.
+ #
+ #log-config-on-start = true
+}
+
+## Secret key
+# http://www.playframework.com/documentation/latest/ApplicationSecret
+# ~~~~~
+# The secret key is used to sign Play's session cookie.
+# This must be changed for production, but we don't recommend you change it in this file.
+play.http.secret.key = a-long-secret-to-calm-the-rage-of-the-entropy-gods
+
+## Modules
+# https://www.playframework.com/documentation/latest/Modules
+# ~~~~~
+# Control which modules are loaded when Play starts. Note that modules are
+# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
+# Please see https://www.playframework.com/documentation/latest/GlobalSettings
+# for more information.
+#
+# You can also extend Play functionality by using one of the publically available
+# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
+play.modules {
+ # By default, Play will load any class called Module that is defined
+ # in the root package (the "app" directory), or you can define them
+ # explicitly below.
+ # If there are any built-in modules that you want to enable, you can list them here.
+ #enabled += my.application.Module
+
+ # If there are any built-in modules that you want to disable, you can list them here.
+ #disabled += ""
+}
+
+## IDE
+# https://www.playframework.com/documentation/latest/IDE
+# ~~~~~
+# Depending on your IDE, you can add a hyperlink for errors that will jump you
+# directly to the code location in the IDE in dev mode. The following line makes
+# use of the IntelliJ IDEA REST interface:
+#play.editor="http://localhost:63342/api/file/?file=%s&line=%s"
+
+## Internationalisation
+# https://www.playframework.com/documentation/latest/JavaI18N
+# https://www.playframework.com/documentation/latest/ScalaI18N
+# ~~~~~
+# Play comes with its own i18n settings, which allow the user's preferred language
+# to map through to internal messages, or allow the language to be stored in a cookie.
+play.i18n {
+ # The application languages
+ langs = [ "en" ]
+
+ # Whether the language cookie should be secure or not
+ #langCookieSecure = true
+
+ # Whether the HTTP only attribute of the cookie should be set to true
+ #langCookieHttpOnly = true
+}
+
+## Play HTTP settings
+# ~~~~~
+play.http {
+ ## Router
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # Define the Router object to use for this application.
+ # This router will be looked up first when the application is starting up,
+ # so make sure this is the entry point.
+ # Furthermore, it's assumed your route file is named properly.
+ # So for an application router like `my.application.Router`,
+ # you may need to define a router file `conf/my.application.routes`.
+ # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
+ #router = my.application.Router
+
+ ## Action Creator
+ # https://www.playframework.com/documentation/latest/JavaActionCreator
+ # ~~~~~
+ #actionCreator = null
+
+ ## ErrorHandler
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # If null, will attempt to load a class called ErrorHandler in the root package,
+ #errorHandler = null
+
+ ## Session & Flash
+ # https://www.playframework.com/documentation/latest/JavaSessionFlash
+ # https://www.playframework.com/documentation/latest/ScalaSessionFlash
+ # ~~~~~
+ session {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+
+ # Sets the max-age field of the cookie to 5 minutes.
+ # NOTE: this only sets when the browser will discard the cookie. Play will consider any
+ # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
+ # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
+ #maxAge = 300
+
+ # Sets the domain on the session cookie.
+ #domain = "example.com"
+ }
+
+ flash {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+ }
+}
+
+## Netty Provider
+# https://www.playframework.com/documentation/latest/SettingsNetty
+# ~~~~~
+play.server.netty {
+ # Whether the Netty wire should be logged
+ log.wire = true
+
+ # If you run Play on Linux, you can use Netty's native socket transport
+ # for higher performance with less garbage.
+ transport = "native"
+}
+
+## WS (HTTP Client)
+# https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS
+# ~~~~~
+# The HTTP client primarily used for REST APIs. The default client can be
+# configured directly, but you can also create different client instances
+# with customized settings. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += ws // or javaWs if using java
+#
+play.ws {
+ # Sets HTTP requests not to follow 302 requests
+ #followRedirects = false
+
+ # Sets the maximum number of open HTTP connections for the client.
+ #ahc.maxConnectionsTotal = 50
+
+ ## WS SSL
+ # https://www.playframework.com/documentation/latest/WsSSL
+ # ~~~~~
+ ssl {
+ # Configuring HTTPS with Play WS does not require programming. You can
+ # set up both trustManager and keyManager for mutual authentication, and
+ # turn on JSSE debugging in development with a reload.
+ #debug.handshake = true
+ #trustManager = {
+ # stores = [
+ # { type = "JKS", path = "exampletrust.jks" }
+ # ]
+ #}
+ }
+}
+
+## Cache
+# https://www.playframework.com/documentation/latest/JavaCache
+# https://www.playframework.com/documentation/latest/ScalaCache
+# ~~~~~
+# Play comes with an integrated cache API that can reduce the operational
+# overhead of repeated requests. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += cache
+#
+play.cache {
+ # If you want to bind several caches, you can bind the individually
+ #bindCaches = ["db-cache", "user-cache", "session-cache"]
+}
+
+## Filter Configuration
+# https://www.playframework.com/documentation/latest/Filters
+# ~~~~~
+# There are a number of built-in filters that can be enabled and configured
+# to give Play greater security.
+#
+play.filters {
+
+ # Enabled filters are run automatically against Play.
+ # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
+ enabled = []
+
+ # Disabled filters remove elements from the enabled list.
+ # disabled += filters.CSRFFilter
+
+
+ ## CORS filter configuration
+ # https://www.playframework.com/documentation/latest/CorsFilter
+ # ~~~~~
+ # CORS is a protocol that allows web applications to make requests from the browser
+ # across different domains.
+ # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
+ # dependencies on CORS settings.
+ cors {
+ # Filter paths by a whitelist of path prefixes
+ #pathPrefixes = ["/some/path", ...]
+
+ # The allowed origins. If null, all origins are allowed.
+ #allowedOrigins = ["http://www.example.com"]
+
+ # The allowed HTTP methods. If null, all methods are allowed
+ #allowedHttpMethods = ["GET", "POST"]
+ }
+
+ ## Security headers filter configuration
+ # https://www.playframework.com/documentation/latest/SecurityHeaders
+ # ~~~~~
+ # Defines security headers that prevent XSS attacks.
+ # If enabled, then all options are set to the below configuration by default:
+ headers {
+ # The X-Frame-Options header. If null, the header is not set.
+ #frameOptions = "DENY"
+
+ # The X-XSS-Protection header. If null, the header is not set.
+ #xssProtection = "1; mode=block"
+
+ # The X-Content-Type-Options header. If null, the header is not set.
+ #contentTypeOptions = "nosniff"
+
+ # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
+ #permittedCrossDomainPolicies = "master-only"
+
+ # The Content-Security-Policy header. If null, the header is not set.
+ #contentSecurityPolicy = "default-src 'self'"
+ }
+
+ ## Allowed hosts filter configuration
+ # https://www.playframework.com/documentation/latest/AllowedHostsFilter
+ # ~~~~~
+ # Play provides a filter that lets you configure which hosts can access your application.
+ # This is useful to prevent cache poisoning attacks.
+ hosts {
+ # Allow requests to example.com, its subdomains, and localhost:9000.
+ #allowed = [".example.com", "localhost:9000"]
+ }
+}
+
+# Learning-Service Configuration
+content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit"]
+
+# Cassandra Configuration
+content.keyspace.name=content_store
+content.keyspace.table=content_data
+#TODO: Add Configuration for assessment. e.g: question_data
+orchestrator.keyspace.name=script_store
+orchestrator.keyspace.table=script_data
+cassandra.lp.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+cassandra.lpa.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+
+# Redis Configuration
+redis.host=localhost
+redis.port=6379
+redis.maxConnections=128
+
+#Condition to enable publish locally
+content.publish_task.enabled=true
+
+#directory location where store unzip file
+dist.directory=/data/tmp/dist/
+output.zipfile=/data/tmp/story.zip
+source.folder=/data/tmp/temp2/
+save.directory=/data/tmp/temp/
+
+# Content 2 vec analytics URL
+CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec"
+
+# FOR CONTENT WORKFLOW PIPELINE (CWP)
+
+#--Content Workflow Pipeline Mode
+OPERATION_MODE=TEST
+
+#--Maximum Content Package File Size Limit in Bytes (50 MB)
+MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800
+
+#--Maximum Asset File Size Limit in Bytes (20 MB)
+MAX_ASSET_FILE_SIZE_LIMIT=20971520
+
+#--No of Retry While File Download Fails
+RETRY_ASSET_DOWNLOAD_COUNT=1
+
+#Google-vision-API
+google.vision.tagging.enabled = false
+
+#Orchestrator env properties
+env="https://dev.ekstep.in/api/learning"
+
+#Current environment
+cloud_storage.env=dev
+
+
+#Folder configuration
+cloud_storage.content.folder=content
+cloud_storage.asset.folder=assets
+cloud_storage.artefact.folder=artifact
+cloud_storage.bundle.folder=bundle
+cloud_storage.media.folder=media
+cloud_storage.ecar.folder=ecar_files
+
+# Media download configuration
+content.media.base.url="https://dev.open-sunbird.org"
+plugin.media.base.url="https://dev.open-sunbird.org"
+
+# Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+
+framework.max_term_creation_limit=200
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+telemetry.search.topn=5
+
+installation.id=ekstep
+
+
+channel.default="in.ekstep"
+
+
+# Language-Code Configuration
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+
+
+framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
+framework.cache.ttl=86400
+framework.cache.read=false
+
+
+# Max size(width/height) of thumbnail in pixels
+max.thumbnail.size.pixels=150
+
+schema.base_path="../../schemas/"
+
+assessment.skip.validation=true
+uestion.keyspace="dev_question_store"
+questionset.keyspace="dev_hierarchy_store"
+cassandra {
+ lp {
+ connection: "127.0.0.1:9042,127.0.0.1:9042,127.0.0.1:9042"
+ }
+ lpa {
+ connection: "127.0.0.1:9042"
+ }
+}
+questionset.keyspace = "dev_hierarchy_store"
+
+import {
+ request_size_limit = 200
+ output_topic_name = "local.auto.creation.job.request"
+ required_props {
+ question = ["name", "code", "mimeType", "framework", "channel"]
+ questionset = ["name", "code", "mimeType", "framework", "channel"]
+ }
+ remove_props {
+ question = []
+ questionseet = []
+ }
+}
+
+
+
diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala
new file mode 100644
index 000000000..bb11e5b5b
--- /dev/null
+++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala
@@ -0,0 +1,45 @@
+package org.sunbird.actors
+
+import java.util
+import java.util.concurrent.TimeUnit
+
+import akka.actor.{ActorSystem, Props}
+import akka.testkit.TestKit
+import org.scalatest.{FlatSpec, Matchers}
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+
+import scala.concurrent.duration.FiniteDuration
+
+class BaseSpec extends FlatSpec with Matchers {
+ val system = ActorSystem.create("system")
+
+ def testUnknownOperation(props: Props, request: Request)(implicit oec: OntologyEngineContext) = {
+ request.setOperation("unknown")
+ val response = callActor(request, props)
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+ def callActor(request: Request, props: Props): Response = {
+ val probe = new TestKit(system)
+ val actorRef = system.actorOf(props)
+ actorRef.tell(request, probe.testActor)
+ probe.expectMsgType[Response](FiniteDuration.apply(100, TimeUnit.SECONDS))
+ }
+
+ def getNode(objectType: String, metadata: Option[util.Map[String, AnyRef]]): Node = {
+ val node = new Node("domain", "DATA_NODE", objectType)
+ node.setGraphId("domain")
+ val nodeMetadata = metadata.getOrElse(new util.HashMap[String, AnyRef]() {{
+ put("name", "Sunbird Node")
+ put("code", "sunbird-node")
+ put("status", "Draft")
+ }})
+ node.setNodeType("DATA_NODE")
+ node.setMetadata(nodeMetadata)
+ node.setObjectType(objectType)
+ node.setIdentifier("test_id")
+ node
+ }
+}
diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionActorTest.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionActorTest.scala
new file mode 100644
index 000000000..be238503d
--- /dev/null
+++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionActorTest.scala
@@ -0,0 +1,233 @@
+package org.sunbird.actors
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.common.HttpUtil
+import org.sunbird.common.dto.ResponseHandler
+import org.sunbird.common.dto.{Property, Request, Response}
+import org.sunbird.graph.dac.model.{Node, SearchCriteria}
+import org.sunbird.graph.utils.ScalaJsonUtils
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.kafka.client.KafkaClient
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class QuestionActorTest extends BaseSpec with MockFactory {
+
+ "questionActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new QuestionActor()), getQuestionRequest())
+ }
+
+ it should "return success response for 'createQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response())).anyNumberOfTimes()
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("channel"-> "in.ekstep","name" -> "New Content", "code" -> "1234", "mimeType"-> "application/vnd.sunbird.question", "primaryCategory" -> "Multiple Choice Question", "visibility" -> "Default")))
+ request.setOperation("createQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'readQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = getNode("Question", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "fields" -> "")))
+ request.setOperation("readQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'updateQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Multiple Choice Question", "name" -> "Updated New Content", "code" -> "1234", "mimeType"-> "application/vnd.sunbird.question").asJava)
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map( "versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("updateQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'reviewQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Multiple Choice Question", "name" -> "Updated New Content", "code" -> "1234", "mimeType"-> "application/vnd.sunbird.question").asJava)
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map( "versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("reviewQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'retireQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Practice Question Set", "name" -> "Updated New Content", "code" -> "1234", "mimeType"-> "application/vnd.sunbird.question").asJava)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node]))
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map( "versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("retireQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'publishQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ val kfClient = mock[KafkaClient]
+ (oec.kafkaClient _).expects().returns(kfClient).anyNumberOfTimes()
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Practice Question Set", "name" -> "Updated New Content", "code" -> "1234", "mimeType"-> "application/vnd.sunbird.question").asJava)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (kfClient.send(_:String, _:String)).expects(*,*).once()
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map( "versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("publishQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "send events to kafka topic" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val kfClient = mock[KafkaClient]
+ val hUtil = mock[HttpUtil]
+ (oec.httpUtil _).expects().returns(hUtil)
+ val resp :Response = ResponseHandler.OK()
+ resp.put("question", new util.HashMap[String, AnyRef](){{
+ put("framework", "NCF")
+ put("channel", "test")
+ }})
+ (hUtil.get(_: String, _: String, _: util.Map[String, String])).expects(*, *, *).returns(resp)
+ (oec.kafkaClient _).expects().returns(kfClient)
+ (kfClient.send(_: String, _: String)).expects(*, *).returns(None)
+ val request = getQuestionRequest()
+ request.getRequest.put("question", new util.HashMap[String, AnyRef](){{
+ put("source", "https://dock.sunbirded.org/api/question/v1/read/do_11307822356267827219477")
+ put("metadata", new util.HashMap[String, AnyRef](){{
+ put("name", "Test Question")
+ put("description", "Test Question")
+ put("mimeType", "application/vnd.sunbird.question")
+ put("code", "test.ques.1")
+ put("primaryCategory", "Learning Resource")
+ }})
+ }})
+ request.setOperation("importQuestion")
+ request.setObjectType("Question")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert(response.get("processId") != null)
+ }
+
+ it should "return success response for 'systemUpdateQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Multiple Choice Question", "name" -> "Updated New Content", "code" -> "1234", "mimeType" -> "application/vnd.sunbird.question").asJava)
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response())).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node))).once()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ val request = getQuestionRequest()
+ request.getContext.put("identifier", "test_id")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("systemUpdateQuestion")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'listQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Question", None)
+ node.getMetadata.putAll(Map("versionKey" -> "1234", "primaryCategory" -> "Multiple Choice Question", "name" -> "Updated New Content", "code" -> "1234", "mimeType" -> "application/vnd.sunbird.question").asJava)
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response())).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node))).once()
+ val request = getQuestionRequest()
+ request.put("identifiers", util.Arrays.asList( "test_id"))
+ request.put("identifier", util.Arrays.asList( "test_id"))
+ request.put("fields", "")
+ request.setOperation("listQuestions")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "throw exception for 'listQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getQuestionRequest()
+ request.put("identifier", null)
+ request.put("fields", "")
+ request.setOperation("listQuestions")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert(response.getResponseCode.code == 400)
+ }
+
+ it should "throw client exception for 'listQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getQuestionRequest()
+ request.put("identifiers", util.Arrays.asList( "test_id_1","test_id_2","test_id_3","test_id_4","test_id_5","test_id_6","test_id_7","test_id_8","test_id_9","test_id_10","test_id_11","test_id_12","test_id_13","test_id_14","test_id_15","test_id_16","test_id_17","test_id_18","test_id_19","test_id_20","test_id_21"))
+ request.setOperation("listQuestions")
+ request.put("fields", "")
+ val response = callActor(request, Props(new QuestionActor()))
+ assert(response.getResponseCode.code == 400)
+ }
+
+ private def getQuestionRequest(): Request = {
+ val request = new Request()
+ request.setContext(new java.util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Question")
+ put("schemaName", "question")
+ }
+ })
+ request.setObjectType("Question")
+ request
+ }
+
+ def getDefinitionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:practice-question-set_question_all")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("ObjectCategoryDefinition")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String,AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:practice_question_set\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"license\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+}
\ No newline at end of file
diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala
new file mode 100644
index 000000000..578b1af51
--- /dev/null
+++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala
@@ -0,0 +1,584 @@
+package org.sunbird.actors
+
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.common.HttpUtil
+import org.sunbird.common.dto.{Property, Request, Response, ResponseHandler}
+import org.sunbird.graph.dac.model.{Node, Relation, SearchCriteria}
+import org.sunbird.graph.nodes.DataNode.getRelationMap
+import org.sunbird.graph.utils.ScalaJsonUtils
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.kafka.client.KafkaClient
+import org.sunbird.utils.JavaJsonUtils
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class QuestionSetActorTest extends BaseSpec with MockFactory {
+
+ "questionSetActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new QuestionSetActor()), getQuestionSetRequest())
+ }
+
+ it should "return success response for 'createQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response())).anyNumberOfTimes()
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ request.setOperation("createQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'readQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", Some(new util.HashMap[String, AnyRef]() {
+ {
+ put("name", "QuestionSet")
+ put("description", "Updated question Set")
+ }
+ }))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "fields" -> "")))
+ request.setOperation("readQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'updateQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "description" -> "Updated description",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("updateQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'reviewQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.updateExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ (graphDB.updateNodes(_:String, _:util.List[String], _: util.Map[String, AnyRef])).expects(*, *, *).returns(Future(Map[String, Node]().asJava)).anyNumberOfTimes
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("reviewQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'retireQuestionSet" +
+ "'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node]))
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("retireQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'publishQuestionSet" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ val kfClient = mock[KafkaClient]
+ (oec.kafkaClient _).expects().returns(kfClient).anyNumberOfTimes()
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (kfClient.send(_: String, _: String)).expects(*, *).once()
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("publishQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'addQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.setIdentifier("do_1234")
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node).asJava)).anyNumberOfTimes()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.saveExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll((Map("children" -> List("do_749").asJava.asInstanceOf[AnyRef], "rootId" -> "do1234")).asJava)
+ request.setOperation("addQuestion")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'removeQuestion'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.setIdentifier("do_1234")
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node).asJava)).anyNumberOfTimes()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.saveExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll((Map("children" -> List("do_914").asJava.asInstanceOf[AnyRef], "rootId" -> "do1234")).asJava)
+ request.setOperation("removeQuestion")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return error response for 'updateHierarchyQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getInvalidUpdateHierarchyReq()
+ request.getContext.put("rootId", "do_123")
+ request.setOperation("updateHierarchy")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+
+ it should "return success response for 'updateHierarchyQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val rootNode = getRootNode()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(rootNode))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(rootNode)).anyNumberOfTimes()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getEmptyCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.updateExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ val request = getUpdateHierarchyReq()
+ request.getContext.put("rootId", "do_1234")
+ request.setOperation("updateHierarchy")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+
+ it should "return success response for 'rejectQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "status" -> "Review",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.updateExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ (graphDB.updateNodes(_:String, _:util.List[String], _: util.Map[String, AnyRef])).expects(*, *, *).returns(Future(Map[String, Node]().asJava)).anyNumberOfTimes
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "do1234")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("rejectQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "send events to kafka topic" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val kfClient = mock[KafkaClient]
+ val hUtil = mock[HttpUtil]
+ (oec.httpUtil _).expects().returns(hUtil)
+ val resp :Response = ResponseHandler.OK()
+ resp.put("questionset", new util.HashMap[String, AnyRef](){{
+ put("framework", "NCF")
+ put("channel", "test")
+ }})
+ (hUtil.get(_: String, _: String, _: util.Map[String, String])).expects(*, *, *).returns(resp)
+ (oec.kafkaClient _).expects().returns(kfClient)
+ (kfClient.send(_: String, _: String)).expects(*, *).returns(None)
+ val request = getQuestionSetRequest()
+ request.getRequest.put("questionset", new util.HashMap[String, AnyRef](){{
+ put("source", "https://dock.sunbirded.org/api/questionset/v1/read/do_11307822356267827219477")
+ put("metadata", new util.HashMap[String, AnyRef](){{
+ put("name", "Test QuestionSet")
+ put("description", "Test QuestionSet")
+ put("mimeType", "application/vnd.sunbird.questionset")
+ put("code", "test.ques.1")
+ put("primaryCategory", "Learning Resource")
+ }})
+ }})
+ request.setOperation("importQuestionSet")
+ request.setObjectType("QuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert(response.get("processId") != null)
+ }
+
+ it should "return success response for 'systemUpdateQuestionSet'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ node.setIdentifier("test_id")
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "description" -> "Updated description",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "status" -> "Draft",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node))).once()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "test_id")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("systemUpdateQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'systemUpdateQuestionSet' with image Node" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("QuestionSet", None)
+ val imageNode = getNode("QuestionSet", None)
+ node.setIdentifier("test_id")
+ imageNode.setIdentifier("test_id.img")
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "description" -> "Updated description",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "status" -> "Live",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ imageNode.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "description" -> "Updated description",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "status" -> "Draft",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(List(node, imageNode))).once()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes
+ (graphDB.updateExternalProps(_: Request)).expects(*).returns(Future(new Response())).anyNumberOfTimes
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node)).anyNumberOfTimes()
+ val request = getQuestionSetRequest()
+ request.getContext.put("identifier", "test_id")
+ request.putAll(mapAsJavaMap(Map("versionKey" -> "1234", "description" -> "updated desc")))
+ request.setOperation("systemUpdateQuestionSet")
+ val response = callActor(request, Props(new QuestionSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getQuestionSetRequest(): Request = {
+ val request = new Request()
+ request.setContext(new java.util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "QuestionSet")
+ put("schemaName", "questionset")
+ }
+ })
+ request.setObjectType("QuestionSet")
+ request
+ }
+
+
+ private def getRelationNode(): Node = {
+ val node = new Node()
+ node.setGraphId("domain")
+ node.setIdentifier("do_749")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_749")
+ put("mimeType", "application/vnd.sunbird.question")
+ put("visibility", "Default")
+ put("status", "Draft")
+ put("primaryCategory", "Practice Question Set")
+ }
+ })
+ node.setObjectType("Question")
+ node.setNodeType("DATA_NODE")
+ node
+ }
+
+ private def getRelationNode_1(): Node = {
+ val node = new Node()
+ node.setGraphId("domain")
+ node.setIdentifier("do_914")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_914")
+ put("visibility", "Default")
+ put("mimeType", "application/vnd.sunbird.question")
+ put("status", "Draft")
+ put("primaryCategory", "Practice Question Set")
+ }
+ })
+ node.setObjectType("Question")
+ node.setNodeType("DATA_NODE")
+ node
+ }
+
+ def getDefinitionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:practice-question-set_question-set_all")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("ObjectCategoryDefinition")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String, AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:practice_question_set\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"license\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+
+ def getCassandraHierarchy(): Response = {
+ val hierarchyString: String = """{"status":"Live","children":[{"parent":"do_113165166851596288123","totalQuestions":0,"code":"QS_V_Parent_Old","allowSkip":"No","description":"QS-2_parent","language":["English"],"mimeType":"application/vnd.sunbird.questionset","showHints":"No","createdOn":"2020-12-04T15:31:45.948+0530","objectType":"QuestionSet","primaryCategory":"Practice Question Set","lastUpdatedOn":"2020-12-04T15:31:45.947+0530","showSolutions":"No","identifier":"do_11316516745992601613","lastStatusChangedOn":"2020-12-04T15:31:45.948+0530","requiresSubmit":"No","visibility":"Parent","maxQuestions":0,"index":1,"setType":"materialised","languageCode":["en"],"version":1,"versionKey":"1607076105948","showFeedback":"No","depth":1,"name":"QS_V_Parent_2","navigationMode":"non-linear","shuffle":true,"status":"Draft"},{"parent":"do_113165166851596288123","totalQuestions":0,"code":"QS_V_Parent_New","allowSkip":"No","description":"QS-1_parent","language":["English"],"mimeType":"application/vnd.sunbird.questionset","showHints":"No","createdOn":"2020-12-04T15:31:45.872+0530","objectType":"QuestionSet","primaryCategory":"Practice Question Set","children":[{"parent":"do_11316516745922969611","identifier":"do_11316399038283776016","lastStatusChangedOn":"2020-12-02T23:36:59.783+0530","code":"question.code","visibility":"Default","index":1,"language":["English"],"mimeType":"application/vnd.sunbird.question","languageCode":["en"],"createdOn":"2020-12-02T23:36:59.783+0530","version":1,"objectType":"Question","versionKey":"1606932419783","depth":2,"primaryCategory":"Practice Question Set","name":"question_1","lastUpdatedOn":"2020-12-02T23:36:59.783+0530","status":"Draft"}],"lastUpdatedOn":"2020-12-04T15:31:45.861+0530","showSolutions":"No","identifier":"do_11316516745922969611","lastStatusChangedOn":"2020-12-04T15:31:45.876+0530","requiresSubmit":"No","visibility":"Parent","maxQuestions":0,"index":2,"setType":"materialised","languageCode":["en"],"version":1,"versionKey":"1607076105872","showFeedback":"No","depth":1,"name":"QS_V_Parent_1","navigationMode":"non-linear","shuffle":true,"status":"Draft"},{"identifier":"do_11315445058114355211","parent":"do_113165166851596288123","lastStatusChangedOn":"2020-11-19T12:08:13.854+0530","code":"finemanfine","visibility":"Default","index":4,"language":["English"],"mimeType":"application/vnd.sunbird.question","languageCode":["en"],"createdOn":"2020-11-19T12:08:13.854+0530","version":1,"objectType":"Question","versionKey":"1605767893854","depth":1,"name":"question_1","lastUpdatedOn":"2020-11-19T12:08:13.854+0530","contentType":"Resource","status":"Draft"},{"identifier":"do_11315319237189632011","parent":"do_113165166851596288123","lastStatusChangedOn":"2020-11-17T17:28:23.277+0530","code":"finemanfine","visibility":"Default","index":3,"language":["English"],"mimeType":"application/vnd.sunbird.question","languageCode":["en"],"createdOn":"2020-11-17T17:28:23.277+0530","version":1,"objectType":"Question","versionKey":"1605614303277","depth":1,"name":"question_1","lastUpdatedOn":"2020-11-17T17:28:23.277+0530","contentType":"Resource","status":"Draft"}],"identifier":"do_113165166851596288123"}"""
+ val response = new Response
+ response.put("hierarchy", hierarchyString)
+ }
+
+ def getEmptyCassandraHierarchy(): Response = {
+ val response = new Response
+ response.put("hierarchy", "{}")
+ }
+
+ def getInvalidUpdateHierarchyReq() = {
+ val nodesModified = "{\n \"do_1234\": {\n \"metadata\": {\n \"code\": \"updated_code_of_root\"\n },\n \"root\": true,\n \"isNew\": false\n },\n \"QS_V_Parent_New\": {\n \"metadata\": {\n \"code\": \"QS_V_Parent\",\n \"name\": \"QS_V_Parent_1\",\n \"description\": \"QS-1_parent\",\n \"mimeType\": \"application/vnd.sunbird.questionset\",\n \"visibility\": \"Parent\",\n \"primaryCategory\": \"Practice Question Set\"\n },\n \"root\": false,\n \"objectType\": \"QuestionSet\",\n \"isNew\": true\n },\n \"QS_V_Parent_Old\": {\n \"metadata\": {\n \"code\": \"QS_V_Parent\",\n \"name\": \"QS_V_Parent_2\",\n \"description\": \"QS-2_parent\",\n \"mimeType\": \"application/vnd.sunbird.questionset\",\n \"visibility\": \"Parent\",\n \"primaryCategory\": \"Practice Question Set\"\n },\n \"root\": false,\n \"objectType\": \"QuestionSet\",\n \"isNew\": true\n },\n \"do_113178560758022144113\": {\n \"metadata\": {\n \"code\": \"Q_NEW_PARENT\",\n \"name\": \"Q_NEW_PARENT\",\n \"description\": \"Q_NEW_PARENT\",\n \"mimeType\": \"application/vnd.sunbird.question\",\n \"visibility\": \"Parent\",\n \"primaryCategory\": \"Practice Question Set\"\n },\n \"root\": false,\n \"objectType\": \"Question\",\n \"isNew\": true\n }\n }"
+ val hierarchy = "{\n \"do_1234\": {\n \"children\": [\n \"QS_V_Parent_Old\",\n \"QS_V_Parent_New\"\n ],\n \"root\": true\n },\n \"QS_V_Parent_Old\": {\n \"children\": [],\n \"root\": false\n },\n \"QS_V_Parent_New\": {\n \"children\": [\n \"do_113178560758022144113\"\n ],\n \"root\": false\n },\n \"do_113178560758022144113\": {\n\n }\n }"
+ val request = getQuestionSetRequest()
+ request.put("nodesModified", JavaJsonUtils.deserialize[java.util.Map[String, AnyRef]](nodesModified))
+ request.put("hierarchy", JavaJsonUtils.deserialize[java.util.Map[String, AnyRef]](hierarchy))
+ request
+ }
+
+ def getUpdateHierarchyReq() = {
+ val nodesModified =
+ """
+ |{
+ | "UUID": {
+ | "metadata": {
+ | "mimeType": "application/vnd.sunbird.questionset",
+ | "name": "Subjective",
+ | "primaryCategory": "Practice Question Set",
+ | "code": "questionset"
+ | },
+ | "objectType": "QuestionSet",
+ | "root": false,
+ | "isNew": true
+ | }
+ | }
+ """.stripMargin
+ val hierarchy =
+ """
+ |{
+ | "do_1234": {
+ | "children": [
+ | "UUID"
+ | ],
+ | "root": true
+ | }
+ | }
+ """.stripMargin
+ val request = getQuestionSetRequest()
+ request.put("nodesModified", JavaJsonUtils.deserialize[java.util.Map[String, AnyRef]](nodesModified))
+ request.put("hierarchy", JavaJsonUtils.deserialize[java.util.Map[String, AnyRef]](hierarchy))
+ request
+ }
+
+ def getRootNode(): Node = {
+ val node = getNode("QuestionSet", None)
+ node.setIdentifier("do_1234")
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "navigationMode" -> "linear",
+ "allowSkip" -> "Yes",
+ "requiresSubmit" -> "No",
+ "shuffle" -> true.asInstanceOf[AnyRef],
+ "showFeedback" -> "Yes",
+ "showSolutions" -> "Yes",
+ "showHints" -> "Yes",
+ "summaryType" -> "Complete",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set",
+ "channel" -> "in.ekstep"
+ )))
+ node
+ }
+
+ def getQuestionSetNode(identifier:String): Node = {
+ val node = getNode("QuestionSet", None)
+ node.setIdentifier(identifier)
+ node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1",
+ "visibility" -> "Default",
+ "code" -> "finemanfine",
+ "versionKey" -> "1234",
+ "mimeType" -> "application/vnd.sunbird.questionset",
+ "primaryCategory" -> "Practice Question Set")))
+ node
+ }
+}
\ No newline at end of file
diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/TestItemSetActor.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/TestItemSetActor.scala
new file mode 100644
index 000000000..3c4602feb
--- /dev/null
+++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/TestItemSetActor.scala
@@ -0,0 +1,132 @@
+package org.sunbird.actors
+
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.common.dto.Request
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestItemSetActor extends BaseSpec with MockFactory {
+
+ "ItemSetActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new ItemSetActor()), getItemSetRequest())
+ }
+
+ it should "create a itemSetNode and store it in neo4j" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getValidNode()))
+ val request = getItemSetRequest()
+ request.setRequest(mapAsJavaMap(Map("name" -> "test-itemset", "code" -> "1234")))
+ request.setOperation("createItemSet")
+ val response = callActor(request, Props(new ItemSetActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("identifier").equals("1234"))
+ }
+
+ it should "return success response for updateItemSet" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getItemSetRequest()
+ request.getContext.put("identifier", "1234")
+ request.put("name", "test")
+ request.put("code", "1234")
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc")))
+ request.setOperation("updateItemSet")
+ val response = callActor(request, Props(new ItemSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("1234"))
+ }
+
+
+ it should "return success response for readItemSet" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(1)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ val request = getItemSetRequest()
+ request.getContext.put("identifier", "1234")
+ request.putAll(mapAsJavaMap(Map("fields" -> "")))
+ request.setOperation("readItemSet")
+ val response = callActor(request, Props(new ItemSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("itemset").asInstanceOf[util.Map[String, AnyRef]].get("identifier").equals("1234"))
+ }
+
+ it should "return success response for reviewItemSet" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(3)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getItemSetRequest()
+ request.getContext.put("identifier","do_1234")
+ request.put("name", "test")
+ request.put("code", "1234")
+ request.setOperation("reviewItemSet")
+ val response = callActor(request, Props(new ItemSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("1234"))
+ }
+
+ it should "return success response for retireItemSet" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getItemSetRequest()
+ request.getContext.put("identifier","do_1234")
+ request.put("name", "test")
+ request.put("code", "1234")
+ request.setOperation("retireItemSet")
+ val response = callActor(request, Props(new ItemSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("1234"))
+ }
+
+ private def getItemSetRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "2.0")
+ put("objectType", "ItemSet")
+ put("schemaName", "itemset")
+ }
+ })
+ request.setObjectType("ItemSet")
+ request
+ }
+
+ private def getValidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("1234")
+ node.setNodeType("DATA_NODE")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "1234")
+ put("objectType", "ItemSet")
+ put("name", "test-itemset")
+ }
+ })
+ node.setObjectType("ItemSet")
+ node
+ }
+
+}
diff --git a/assessment-api/assessment-service/app/controllers/BaseController.scala b/assessment-api/assessment-service/app/controllers/BaseController.scala
new file mode 100644
index 000000000..a3b47974b
--- /dev/null
+++ b/assessment-api/assessment-service/app/controllers/BaseController.scala
@@ -0,0 +1,73 @@
+package controllers
+
+import java.util.UUID
+
+import akka.actor.ActorRef
+import akka.pattern.Patterns
+import org.sunbird.common.DateUtils
+import org.sunbird.common.dto.{Response, ResponseHandler}
+import org.sunbird.common.exception.ResponseCode
+import play.api.mvc._
+import utils.JavaJsonUtils
+
+import collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+abstract class BaseController(protected val cc: ControllerComponents)(implicit exec: ExecutionContext) extends AbstractController(cc) {
+
+ def requestBody()(implicit request: Request[AnyContent]) = {
+ val body = request.body.asJson.getOrElse("{}").toString
+ JavaJsonUtils.deserialize[java.util.Map[String, Object]](body).getOrDefault("request", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ }
+
+ def commonHeaders()(implicit request: Request[AnyContent]): java.util.Map[String, Object] = {
+ val customHeaders = Map("x-channel-id" -> "channel", "X-Consumer-ID" -> "consumerId", "X-App-Id" -> "appId")
+ customHeaders.map(ch => {
+ val value = request.headers.get(ch._1)
+ if (value.isDefined && !value.isEmpty) {
+ collection.mutable.HashMap[String, Object](ch._2 -> value.get).asJava
+ } else {
+ collection.mutable.HashMap[String, Object]().asJava
+ }
+ }).reduce((a, b) => {
+ a.putAll(b)
+ return a
+ })
+ }
+
+ def getRequest(input: java.util.Map[String, AnyRef], context: java.util.Map[String, AnyRef], operation: String): org.sunbird.common.dto.Request = {
+ new org.sunbird.common.dto.Request(context, input, operation, null);
+ }
+
+ def getResult(apiId: String, actor: ActorRef, request: org.sunbird.common.dto.Request) : Future[Result] = {
+ val future = Patterns.ask(actor, request, 30000) recoverWith {case e: Exception => Future(ResponseHandler.getErrorResponse(e))}
+ future.map(f => {
+ val result = f.asInstanceOf[Response]
+ result.setId(apiId)
+ setResponseEnvelope(result)
+ val response = JavaJsonUtils.serialize(result);
+ result.getResponseCode match {
+ case ResponseCode.OK => Ok(response).as("application/json")
+ case ResponseCode.CLIENT_ERROR => BadRequest(response).as("application/json")
+ case ResponseCode.RESOURCE_NOT_FOUND => NotFound(response).as("application/json")
+ case _ => play.api.mvc.Results.InternalServerError(response).as("application/json")
+ }
+ })
+ }
+
+ def setResponseEnvelope(response: Response) = {
+ response.setTs(DateUtils.formatCurrentDate("yyyy-MM-dd'T'HH:mm:ss'Z'XXX"))
+ response.getParams.setResmsgid(UUID.randomUUID().toString)
+ }
+
+ def setRequestContext(request:org.sunbird.common.dto.Request, version: String, objectType: String, schemaName: String): Unit = {
+ var contextMap: java.util.Map[String, AnyRef] = new java.util.HashMap[String, AnyRef](){{
+ put("graph_id", "domain")
+ put("version" , version)
+ put("objectType" , objectType)
+ put("schemaName", schemaName)
+ }};
+ request.setObjectType(objectType);
+ request.setContext(contextMap)
+ }
+}
diff --git a/assessment-api/assessment-service/app/controllers/HealthController.scala b/assessment-api/assessment-service/app/controllers/HealthController.scala
new file mode 100644
index 000000000..d278b7681
--- /dev/null
+++ b/assessment-api/assessment-service/app/controllers/HealthController.scala
@@ -0,0 +1,31 @@
+package controllers
+
+import akka.actor.{ActorRef, ActorSystem}
+import handlers.SignalHandler
+import javax.inject._
+import org.sunbird.common.JsonUtils
+import org.sunbird.common.dto.ResponseHandler
+import play.api.mvc._
+import utils.{ActorNames, ApiId}
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class HealthController @Inject()(@Named(ActorNames.HEALTH_ACTOR) healthActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem, signalHandler: SignalHandler)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ def health() = Action.async { implicit request =>
+ if (signalHandler.isShuttingDown) {
+ Future { ServiceUnavailable }
+ } else {
+ getResult(ApiId.APPLICATION_HEALTH, healthActor, new org.sunbird.common.dto.Request())
+ }
+ }
+
+ def serviceHealth() = Action.async { implicit request =>
+ if (signalHandler.isShuttingDown)
+ Future { ServiceUnavailable }
+ else {
+ val response = ResponseHandler.OK().setId(ApiId.APPLICATION_SERVICE_HEALTH).put("healthy", true)
+ Future { Ok(JsonUtils.serialize(response)).as("application/json") }
+ }
+ }
+}
diff --git a/assessment-api/assessment-service/app/controllers/v3/ItemSetController.scala b/assessment-api/assessment-service/app/controllers/v3/ItemSetController.scala
new file mode 100644
index 000000000..4786424a7
--- /dev/null
+++ b/assessment-api/assessment-service/app/controllers/v3/ItemSetController.scala
@@ -0,0 +1,71 @@
+package controllers.v3
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId, ItemSetOperations}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.ExecutionContext
+
+@Singleton
+class ItemSetController @Inject()(@Named(ActorNames.ITEM_SET_ACTOR) itemSetActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "ItemSet"
+ val schemaName: String = "itemset"
+ val version = "2.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val itemset = body.getOrDefault("itemset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]]
+ itemset.putAll(headers)
+ val itemSetRequest = getRequest(itemset, headers, ItemSetOperations.createItemSet.toString)
+ setRequestContext(itemSetRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_ITEM_SET, itemSetActor, itemSetRequest)
+ }
+
+ def read(identifier: String, fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val itemset = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ itemset.putAll(headers)
+ itemset.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse("")).asJava)
+ val itemSetRequest = getRequest(itemset, headers, ItemSetOperations.readItemSet.toString)
+ setRequestContext(itemSetRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_ITEM_SET, itemSetActor, itemSetRequest)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val itemset = body.getOrDefault("itemset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ itemset.putAll(headers)
+ val itemSetRequest = getRequest(itemset, headers, ItemSetOperations.updateItemSet.toString)
+ setRequestContext(itemSetRequest, version, objectType, schemaName)
+ itemSetRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.UPDATE_ITEM_SET, itemSetActor, itemSetRequest)
+ }
+
+ def review(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val itemset = body.getOrDefault("itemset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ itemset.putAll(headers)
+ val itemSetRequest = getRequest(itemset, headers, ItemSetOperations.reviewItemSet.toString)
+ setRequestContext(itemSetRequest, version, objectType, schemaName)
+ itemSetRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.REVIEW_ITEM_SET, itemSetActor, itemSetRequest)
+ }
+
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val itemset = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ itemset.putAll(headers)
+ val itemSetRequest = getRequest(itemset, headers, ItemSetOperations.retireItemSet.toString)
+ setRequestContext(itemSetRequest, version, objectType, schemaName)
+ itemSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.RETIRE_ITEM_SET, itemSetActor, itemSetRequest)
+ }
+}
diff --git a/assessment-api/assessment-service/app/controllers/v4/QuestionController.scala b/assessment-api/assessment-service/app/controllers/v4/QuestionController.scala
new file mode 100644
index 000000000..b01eb4f2e
--- /dev/null
+++ b/assessment-api/assessment-service/app/controllers/v4/QuestionController.scala
@@ -0,0 +1,112 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId, QuestionOperations}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.ExecutionContext
+
+class QuestionController @Inject()(@Named(ActorNames.QUESTION_ACTOR) questionActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "Question"
+ val schemaName: String = "question"
+ val version = "1.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val question = body.getOrDefault("question", new java.util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]]
+ question.putAll(headers)
+ val questionRequest = getRequest(question, headers, QuestionOperations.createQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_QUESTION, questionActor, questionRequest)
+ }
+
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val question = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ question.putAll(headers)
+ question.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse(""), "mode" -> mode.getOrElse("read")).asJava)
+ val questionRequest = getRequest(question, headers, QuestionOperations.readQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_QUESTION, questionActor, questionRequest)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val question = body.getOrDefault("question", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ question.putAll(headers)
+ val questionRequest = getRequest(question, headers, QuestionOperations.updateQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ questionRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_QUESTION, questionActor, questionRequest)
+ }
+
+ def review(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val question = body.getOrDefault("question", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ question.putAll(headers)
+ val questionRequest = getRequest(question, headers, QuestionOperations.reviewQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ questionRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.REVIEW_QUESTION, questionActor, questionRequest)
+ }
+
+ def publish(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val question = body.getOrDefault("question", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ question.putAll(headers)
+ val questionRequest = getRequest(question, headers, QuestionOperations.publishQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ questionRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.PUBLISH_QUESTION, questionActor, questionRequest)
+ }
+
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val question = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ question.putAll(headers)
+ val questionRequest = getRequest(question, headers, QuestionOperations.retireQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ questionRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.RETIRE_QUESTION, questionActor, questionRequest)
+ }
+
+ def importQuestion() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val questionRequest = getRequest(body, headers, QuestionOperations.importQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ getResult(ApiId.IMPORT_QUESTION, questionActor, questionRequest)
+ }
+
+ def systemUpdate(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ val questionRequest = getRequest(content, headers, QuestionOperations.systemUpdateQuestion.toString)
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ questionRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.SYSTEM_UPDATE_QUESTION, questionActor, questionRequest)
+ }
+
+ def list(fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val question = body.getOrDefault("search", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ question.putAll(headers)
+ question.put("fields", fields.getOrElse(""))
+ val questionRequest = getRequest(question, headers, QuestionOperations.listQuestions.toString)
+ questionRequest.put("identifiers", questionRequest.get("identifier"))
+ setRequestContext(questionRequest, version, objectType, schemaName)
+ getResult(ApiId.LIST_QUESTIONS, questionActor, questionRequest)
+ }
+}
diff --git a/assessment-api/assessment-service/app/controllers/v4/QuestionSetController.scala b/assessment-api/assessment-service/app/controllers/v4/QuestionSetController.scala
new file mode 100644
index 000000000..5577615f8
--- /dev/null
+++ b/assessment-api/assessment-service/app/controllers/v4/QuestionSetController.scala
@@ -0,0 +1,151 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId, QuestionSetOperations}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.ExecutionContext
+
+class QuestionSetController @Inject()(@Named(ActorNames.QUESTION_SET_ACTOR) questionSetActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "QuestionSet"
+ val schemaName: String = "questionset"
+ val version = "1.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]]
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.createQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val questionSet = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ questionSet.putAll(headers)
+ questionSet.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse(""), "mode" -> mode.getOrElse("read")).asJava)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.readQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.updateQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def review(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.reviewQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.REVIEW_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def publish(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.publishQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.PUBLISH_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val questionSet = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.retireQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.RETIRE_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def add() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.addQuestion.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.ADD_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def remove() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.removeQuestion.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.REMOVE_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def updateHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val data = body.getOrDefault("data", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ data.putAll(headers)
+ val questionSetRequest = getRequest(data, headers, "updateHierarchy")
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.UPDATE_HIERARCHY, questionSetActor, questionSetRequest)
+ }
+
+ def getHierarchy(identifier: String, mode: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val questionSet = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ questionSet.putAll(headers)
+ questionSet.putAll(Map("rootId" -> identifier, "mode" -> mode.getOrElse("")).asJava)
+ val readRequest = getRequest(questionSet, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.GET_HIERARCHY, questionSetActor, readRequest)
+ }
+
+ def reject(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.rejectQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.REJECT_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def importQuestionSet() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val questionSetRequest = getRequest(body, headers, QuestionSetOperations.importQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ getResult(ApiId.IMPORT_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+
+ def systemUpdate(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val questionSet = body.getOrDefault("questionset", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ questionSet.putAll(headers)
+ val questionSetRequest = getRequest(questionSet, headers, QuestionSetOperations.systemUpdateQuestionSet.toString)
+ setRequestContext(questionSetRequest, version, objectType, schemaName)
+ questionSetRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.SYSTEM_UPDATE_QUESTION_SET, questionSetActor, questionSetRequest)
+ }
+}
diff --git a/assessment-api/assessment-service/app/filters/AccessLogFilter.scala b/assessment-api/assessment-service/app/filters/AccessLogFilter.scala
new file mode 100644
index 000000000..aad9f8419
--- /dev/null
+++ b/assessment-api/assessment-service/app/filters/AccessLogFilter.scala
@@ -0,0 +1,45 @@
+package filters
+
+import akka.util.ByteString
+import javax.inject.Inject
+import org.sunbird.telemetry.util.TelemetryAccessEventUtil
+import play.api.Logging
+import play.api.libs.streams.Accumulator
+import play.api.mvc._
+
+import scala.concurrent.ExecutionContext
+import scala.collection.JavaConverters._
+
+class AccessLogFilter @Inject() (implicit ec: ExecutionContext) extends EssentialFilter with Logging {
+
+ val xHeaderNames = Map("x-session-id" -> "X-Session-ID", "X-Consumer-ID" -> "x-consumer-id", "x-device-id" -> "X-Device-ID", "x-app-id" -> "APP_ID", "x-authenticated-userid" -> "X-Authenticated-Userid", "x-channel-id" -> "X-Channel-Id")
+
+ def apply(nextFilter: EssentialAction) = new EssentialAction {
+ def apply(requestHeader: RequestHeader) = {
+
+ val startTime = System.currentTimeMillis
+
+ val accumulator: Accumulator[ByteString, Result] = nextFilter(requestHeader)
+
+ accumulator.map { result =>
+ val endTime = System.currentTimeMillis
+ val requestTime = endTime - startTime
+
+ val path = requestHeader.uri
+ if(!path.contains("/health")){
+ val headers = requestHeader.headers.headers.groupBy(_._1).mapValues(_.map(_._2))
+ val appHeaders = headers.filter(header => xHeaderNames.keySet.contains(header._1.toLowerCase))
+ .map(entry => (xHeaderNames.get(entry._1.toLowerCase()).get, entry._2.head))
+ val otherDetails = Map[String, Any]("StartTime" -> startTime, "env" -> "assessment",
+ "RemoteAddress" -> requestHeader.remoteAddress,
+ "ContentLength" -> result.body.contentLength.getOrElse(0),
+ "Status" -> result.header.status, "Protocol" -> "http",
+ "path" -> path,
+ "Method" -> requestHeader.method.toString)
+ TelemetryAccessEventUtil.writeTelemetryEventLog((otherDetails ++ appHeaders).asInstanceOf[Map[String, AnyRef]].asJava)
+ }
+ result.withHeaders("Request-Time" -> requestTime.toString)
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/assessment-api/assessment-service/app/handlers/SignalHandler.scala b/assessment-api/assessment-service/app/handlers/SignalHandler.scala
new file mode 100644
index 000000000..4cad301c1
--- /dev/null
+++ b/assessment-api/assessment-service/app/handlers/SignalHandler.scala
@@ -0,0 +1,33 @@
+package handlers
+
+import java.util.concurrent.TimeUnit
+
+import akka.actor.ActorSystem
+import javax.inject.{Inject, Singleton}
+import org.slf4j.LoggerFactory
+import play.api.inject.DefaultApplicationLifecycle
+import sun.misc.Signal
+
+import scala.concurrent.duration.Duration
+
+@Singleton
+class SignalHandler @Inject()(implicit actorSystem: ActorSystem, lifecycle: DefaultApplicationLifecycle) {
+ val LOG = LoggerFactory.getLogger(classOf[SignalHandler])
+ val STOP_DELAY = Duration.create(30, TimeUnit.SECONDS)
+ var isShuttingDown = false
+
+ println("Initializing SignalHandler...")
+ Signal.handle(new Signal("TERM"), new sun.misc.SignalHandler() {
+ override def handle(signal: Signal): Unit = {
+ // $COVERAGE-OFF$ Disabling scoverage as this code is impossible to test
+ isShuttingDown = true
+ println("Termination required, swallowing SIGTERM to allow current requests to finish. : " + System.currentTimeMillis())
+ actorSystem.scheduler.scheduleOnce(STOP_DELAY)(() => {
+ println("ApplicationLifecycle stop triggered... : " + System.currentTimeMillis())
+ lifecycle.stop()
+ })(actorSystem.dispatcher)
+ // $COVERAGE-ON
+ }
+ })
+}
+
diff --git a/assessment-api/assessment-service/app/modules/AssessmentModule.scala b/assessment-api/assessment-service/app/modules/AssessmentModule.scala
new file mode 100644
index 000000000..36d3ee2bb
--- /dev/null
+++ b/assessment-api/assessment-service/app/modules/AssessmentModule.scala
@@ -0,0 +1,18 @@
+package modules
+
+import com.google.inject.AbstractModule
+import org.sunbird.actors.{HealthActor, ItemSetActor, QuestionActor, QuestionSetActor}
+import play.libs.akka.AkkaGuiceSupport
+import utils.ActorNames
+
+class AssessmentModule extends AbstractModule with AkkaGuiceSupport {
+
+ override def configure() = {
+ super.configure()
+ bindActor(classOf[HealthActor], ActorNames.HEALTH_ACTOR)
+ bindActor(classOf[ItemSetActor], ActorNames.ITEM_SET_ACTOR)
+ bindActor(classOf[QuestionActor], ActorNames.QUESTION_ACTOR)
+ bindActor(classOf[QuestionSetActor], ActorNames.QUESTION_SET_ACTOR)
+ println("Initialized application actors for assessment-service")
+ }
+}
diff --git a/assessment-api/assessment-service/app/utils/ActorNames.scala b/assessment-api/assessment-service/app/utils/ActorNames.scala
new file mode 100644
index 000000000..16a5cb726
--- /dev/null
+++ b/assessment-api/assessment-service/app/utils/ActorNames.scala
@@ -0,0 +1,10 @@
+package utils
+
+object ActorNames {
+
+ final val HEALTH_ACTOR = "healthActor"
+ final val ITEM_SET_ACTOR = "itemSetActor"
+ final val QUESTION_ACTOR = "questionActor"
+ final val QUESTION_SET_ACTOR = "questionSetActor"
+
+}
diff --git a/assessment-api/assessment-service/app/utils/ApiId.scala b/assessment-api/assessment-service/app/utils/ApiId.scala
new file mode 100644
index 000000000..af128df12
--- /dev/null
+++ b/assessment-api/assessment-service/app/utils/ApiId.scala
@@ -0,0 +1,41 @@
+package utils
+
+object ApiId {
+
+ final val APPLICATION_HEALTH = "api.assessment.health"
+ final val APPLICATION_SERVICE_HEALTH = "api.assessment.service.health"
+
+ //ItemSet APIs
+ val CREATE_ITEM_SET = "api.itemset.create"
+ val READ_ITEM_SET = "api.itemset.read"
+ val UPDATE_ITEM_SET = "api.itemset.update"
+ val REVIEW_ITEM_SET = "api.itemset.review"
+ val RETIRE_ITEM_SET = "api.itemset.retire"
+
+ //Question APIs
+ val CREATE_QUESTION = "api.question.create"
+ val READ_QUESTION = "api.question.read"
+ val UPDATE_QUESTION = "api.question.update"
+ val REVIEW_QUESTION = "api.question.review"
+ val PUBLISH_QUESTION = "api.question.publish"
+ val RETIRE_QUESTION = "api.question.retire"
+ val IMPORT_QUESTION = "api.question.import"
+ val SYSTEM_UPDATE_QUESTION = "api.question.system.update"
+ val LIST_QUESTIONS = "api.questions.list"
+
+ //QuestionSet APIs
+ val CREATE_QUESTION_SET = "api.questionset.create"
+ val READ_QUESTION_SET = "api.questionset.read"
+ val UPDATE_QUESTION_SET = "api.questionset.update"
+ val REVIEW_QUESTION_SET = "api.questionset.review"
+ val PUBLISH_QUESTION_SET = "api.questionset.publish"
+ val RETIRE_QUESTION_SET = "api.questionset.retire"
+ val ADD_QUESTION_SET = "api.questionset.add"
+ val REMOVE_QUESTION_SET = "api.questionset.remove"
+ val UPDATE_HIERARCHY = "api.questionset.hierarchy.update"
+ val GET_HIERARCHY = "api.questionset.hierarchy.get"
+ val REJECT_QUESTION_SET = "api.questionset.reject"
+ val IMPORT_QUESTION_SET = "api.questionset.import"
+ val SYSTEM_UPDATE_QUESTION_SET = "api.questionset.system.update"
+
+}
diff --git a/assessment-api/assessment-service/app/utils/ItemSetOperations.scala b/assessment-api/assessment-service/app/utils/ItemSetOperations.scala
new file mode 100644
index 000000000..4f3fcaa5b
--- /dev/null
+++ b/assessment-api/assessment-service/app/utils/ItemSetOperations.scala
@@ -0,0 +1,5 @@
+package utils
+
+object ItemSetOperations extends Enumeration {
+ val createItemSet, readItemSet, updateItemSet, reviewItemSet, retireItemSet = Value
+}
diff --git a/learning-api/content-service/app/utils/JavaJsonUtils.scala b/assessment-api/assessment-service/app/utils/JavaJsonUtils.scala
similarity index 100%
rename from learning-api/content-service/app/utils/JavaJsonUtils.scala
rename to assessment-api/assessment-service/app/utils/JavaJsonUtils.scala
diff --git a/assessment-api/assessment-service/app/utils/QuestionOperations.scala b/assessment-api/assessment-service/app/utils/QuestionOperations.scala
new file mode 100644
index 000000000..b23ff8c5c
--- /dev/null
+++ b/assessment-api/assessment-service/app/utils/QuestionOperations.scala
@@ -0,0 +1,5 @@
+package utils
+
+object QuestionOperations extends Enumeration {
+ val createQuestion, readQuestion, updateQuestion, reviewQuestion, publishQuestion, retireQuestion, importQuestion, systemUpdateQuestion, listQuestions = Value
+}
diff --git a/assessment-api/assessment-service/app/utils/QuestionSetOperations.scala b/assessment-api/assessment-service/app/utils/QuestionSetOperations.scala
new file mode 100644
index 000000000..08cf26a11
--- /dev/null
+++ b/assessment-api/assessment-service/app/utils/QuestionSetOperations.scala
@@ -0,0 +1,7 @@
+package utils
+
+object QuestionSetOperations extends Enumeration {
+ val createQuestionSet, readQuestionSet, updateQuestionSet, reviewQuestionSet, publishQuestionSet,
+ retireQuestionSet, addQuestion, removeQuestion, updateHierarchyQuestion, readHierarchyQuestion,
+ rejectQuestionSet, importQuestionSet, systemUpdateQuestionSet = Value
+}
diff --git a/assessment-api/assessment-service/conf/application.conf b/assessment-api/assessment-service/conf/application.conf
new file mode 100644
index 000000000..417a6265d
--- /dev/null
+++ b/assessment-api/assessment-service/conf/application.conf
@@ -0,0 +1,409 @@
+# This is the main configuration file for the application.
+# https://www.playframework.com/documentation/latest/ConfigFile
+# ~~~~~
+# Play uses HOCON as its configuration file format. HOCON has a number
+# of advantages over other config formats, but there are two things that
+# can be used when modifying settings.
+#
+# You can include other configuration files in this main application.conf file:
+#include "extra-config.conf"
+#
+# You can declare variables and substitute for them:
+#mykey = ${some.value}
+#
+# And if an environment variable exists when there is no other substitution, then
+# HOCON will fall back to substituting environment variable:
+#mykey = ${JAVA_HOME}
+
+## Akka
+# https://www.playframework.com/documentation/latest/ScalaAkka#Configuration
+# https://www.playframework.com/documentation/latest/JavaAkka#Configuration
+# ~~~~~
+# Play uses Akka internally and exposes Akka Streams and actors in Websockets and
+# other streaming HTTP responses.
+akka {
+ # "akka.log-config-on-start" is extraordinarly useful because it log the complete
+ # configuration at INFO level, including defaults and overrides, so it s worth
+ # putting at the very top.
+ #
+ # Put the following in your conf/logback.xml file:
+ #
+ #
+ #
+ # And then uncomment this line to debug the configuration.
+ #
+ #log-config-on-start = true
+ default-dispatcher {
+ # This will be used if you have set "executor = "fork-join-executor""
+ fork-join-executor {
+ # Min number of threads to cap factor-based parallelism number to
+ parallelism-min = 8
+
+ # The parallelism factor is used to determine thread pool size using the
+ # following formula: ceil(available processors * factor). Resulting size
+ # is then bounded by the parallelism-min and parallelism-max values.
+ parallelism-factor = 32.0
+
+ # Max number of threads to cap factor-based parallelism number to
+ parallelism-max = 64
+
+ # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack
+ # like peeking mode which "pop".
+ task-peeking-mode = "FIFO"
+ }
+ }
+ actors-dispatcher {
+ type = "Dispatcher"
+ executor = "fork-join-executor"
+ fork-join-executor {
+ parallelism-min = 8
+ parallelism-factor = 32.0
+ parallelism-max = 64
+ }
+ # Throughput for default Dispatcher, set to 1 for as fair as possible
+ throughput = 1
+ }
+ actor {
+ deployment {
+ /healthActor
+ {
+ router = smallest-mailbox-pool
+ nr-of-instances = 5
+ dispatcher = actors-dispatcher
+ }
+ /itemSetActor
+ {
+ router = smallest-mailbox-pool
+ nr-of-instances = 2
+ dispatcher = actors-dispatcher
+ }
+ }
+ }
+}
+
+## Secret key
+# http://www.playframework.com/documentation/latest/ApplicationSecret
+# ~~~~~
+# The secret key is used to sign Play's session cookie.
+# This must be changed for production, but we don't recommend you change it in this file.
+play.http.secret.key= a-long-secret-to-calm-the-rage-of-the-entropy-gods
+
+## Modules
+# https://www.playframework.com/documentation/latest/Modules
+# ~~~~~
+# Control which modules are loaded when Play starts. Note that modules are
+# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
+# Please see https://www.playframework.com/documentation/latest/GlobalSettings
+# for more information.
+#
+# You can also extend Play functionality by using one of the publically available
+# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
+play.modules {
+ # By default, Play will load any class called Module that is defined
+ # in the root package (the "app" directory), or you can define them
+ # explicitly below.
+ # If there are any built-in modules that you want to enable, you can list them here.
+ #enabled += my.application.Module
+
+ # If there are any built-in modules that you want to disable, you can list them here.
+ #disabled += ""
+ enabled += modules.AssessmentModule
+}
+
+## IDE
+# https://www.playframework.com/documentation/latest/IDE
+# ~~~~~
+# Depending on your IDE, you can add a hyperlink for errors that will jump you
+# directly to the code location in the IDE in dev mode. The following line makes
+# use of the IntelliJ IDEA REST interface:
+#play.editor="http://localhost:63342/api/file/?file=%s&line=%s"
+
+## Internationalisation
+# https://www.playframework.com/documentation/latest/JavaI18N
+# https://www.playframework.com/documentation/latest/ScalaI18N
+# ~~~~~
+# Play comes with its own i18n settings, which allow the user's preferred language
+# to map through to internal messages, or allow the language to be stored in a cookie.
+play.i18n {
+ # The application languages
+ langs = [ "en" ]
+
+ # Whether the language cookie should be secure or not
+ #langCookieSecure = true
+
+ # Whether the HTTP only attribute of the cookie should be set to true
+ #langCookieHttpOnly = true
+}
+
+## Play HTTP settings
+# ~~~~~
+play.http {
+ ## Router
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # Define the Router object to use for this application.
+ # This router will be looked up first when the application is starting up,
+ # so make sure this is the entry point.
+ # Furthermore, it's assumed your route file is named properly.
+ # So for an application router like `my.application.Router`,
+ # you may need to define a router file `conf/my.application.routes`.
+ # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
+ #router = my.application.Router
+
+ ## Action Creator
+ # https://www.playframework.com/documentation/latest/JavaActionCreator
+ # ~~~~~
+ #actionCreator = null
+
+ ## ErrorHandler
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # If null, will attempt to load a class called ErrorHandler in the root package,
+ #errorHandler = null
+
+ ## Session & Flash
+ # https://www.playframework.com/documentation/latest/JavaSessionFlash
+ # https://www.playframework.com/documentation/latest/ScalaSessionFlash
+ # ~~~~~
+ session {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+
+ # Sets the max-age field of the cookie to 5 minutes.
+ # NOTE: this only sets when the browser will discard the cookie. Play will consider any
+ # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
+ # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
+ #maxAge = 300
+
+ # Sets the domain on the session cookie.
+ #domain = "example.com"
+ }
+
+ flash {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+ }
+}
+
+play.server.http.idleTimeout = 60s
+play.http.parser.maxDiskBuffer = 10MB
+parsers.anyContent.maxLength = 10MB
+
+## Netty Provider
+# https://www.playframework.com/documentation/latest/SettingsNetty
+# ~~~~~
+play.server.netty {
+ # Whether the Netty wire should be logged
+ log.wire = true
+
+ # If you run Play on Linux, you can use Netty's native socket transport
+ # for higher performance with less garbage.
+ transport = "native"
+}
+
+## WS (HTTP Client)
+# https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS
+# ~~~~~
+# The HTTP client primarily used for REST APIs. The default client can be
+# configured directly, but you can also create different client instances
+# with customized settings. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += ws // or javaWs if using java
+#
+play.ws {
+ # Sets HTTP requests not to follow 302 requests
+ #followRedirects = false
+
+ # Sets the maximum number of open HTTP connections for the client.
+ #ahc.maxConnectionsTotal = 50
+
+ ## WS SSL
+ # https://www.playframework.com/documentation/latest/WsSSL
+ # ~~~~~
+ ssl {
+ # Configuring HTTPS with Play WS does not require programming. You can
+ # set up both trustManager and keyManager for mutual authentication, and
+ # turn on JSSE debugging in development with a reload.
+ #debug.handshake = true
+ #trustManager = {
+ # stores = [
+ # { type = "JKS", path = "exampletrust.jks" }
+ # ]
+ #}
+ }
+}
+
+## Cache
+# https://www.playframework.com/documentation/latest/JavaCache
+# https://www.playframework.com/documentation/latest/ScalaCache
+# ~~~~~
+# Play comes with an integrated cache API that can reduce the operational
+# overhead of repeated requests. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += cache
+#
+play.cache {
+ # If you want to bind several caches, you can bind the individually
+ #bindCaches = ["db-cache", "user-cache", "session-cache"]
+}
+
+## Filter Configuration
+# https://www.playframework.com/documentation/latest/Filters
+# ~~~~~
+# There are a number of built-in filters that can be enabled and configured
+# to give Play greater security.
+#
+play.filters {
+
+ # Enabled filters are run automatically against Play.
+ # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
+ enabled = [filters.AccessLogFilter]
+
+ # Disabled filters remove elements from the enabled list.
+ # disabled += filters.CSRFFilter
+
+
+ ## CORS filter configuration
+ # https://www.playframework.com/documentation/latest/CorsFilter
+ # ~~~~~
+ # CORS is a protocol that allows web applications to make requests from the browser
+ # across different domains.
+ # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
+ # dependencies on CORS settings.
+ cors {
+ # Filter paths by a whitelist of path prefixes
+ #pathPrefixes = ["/some/path", ...]
+
+ # The allowed origins. If null, all origins are allowed.
+ #allowedOrigins = ["http://www.example.com"]
+
+ # The allowed HTTP methods. If null, all methods are allowed
+ #allowedHttpMethods = ["GET", "POST"]
+ }
+
+ ## Security headers filter configuration
+ # https://www.playframework.com/documentation/latest/SecurityHeaders
+ # ~~~~~
+ # Defines security headers that prevent XSS attacks.
+ # If enabled, then all options are set to the below configuration by default:
+ headers {
+ # The X-Frame-Options header. If null, the header is not set.
+ #frameOptions = "DENY"
+
+ # The X-XSS-Protection header. If null, the header is not set.
+ #xssProtection = "1; mode=block"
+
+ # The X-Content-Type-Options header. If null, the header is not set.
+ #contentTypeOptions = "nosniff"
+
+ # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
+ #permittedCrossDomainPolicies = "master-only"
+
+ # The Content-Security-Policy header. If null, the header is not set.
+ #contentSecurityPolicy = "default-src 'self'"
+ }
+
+ ## Allowed hosts filter configuration
+ # https://www.playframework.com/documentation/latest/AllowedHostsFilter
+ # ~~~~~
+ # Play provides a filter that lets you configure which hosts can access your application.
+ # This is useful to prevent cache poisoning attacks.
+ hosts {
+ # Allow requests to example.com, its subdomains, and localhost:9000.
+ #allowed = [".example.com", "localhost:9000"]
+ }
+}
+
+play.http.parser.maxMemoryBuffer = 50MB
+akka.http.parsing.max-content-length = 50MB
+schema.base_path="../../schemas/"
+
+# Cassandra Configuration
+cassandra.lp.connection="127.0.0.1:9042"
+content.keyspace = "content_store"
+
+# Redis Configuration
+redis.host="localhost"
+redis.port=6379
+redis.maxConnections=128
+
+# Graph Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+
+installation.id=ekstep
+
+
+languageCode {
+ assamese : "as"
+ bengali : "bn"
+ english : "en"
+ gujarati : "gu"
+ hindi : "hi"
+ kannada : "ka"
+ marathi : "mr"
+ odia : "or"
+ tamil : "ta"
+ telugu : "te"
+}
+
+kafka {
+ urls : "localhost:9092"
+ topic.send.enable : true
+ topics.instruction : "sunbirddev.assessment.publish.request"
+}
+objectcategorydefinition.keyspace="category_store"
+question {
+ keyspace = "question_store"
+ list.limit=20
+}
+questionset.keyspace="hierarchy_store"
+
+cassandra {
+ lp {
+ connection: "localhost:9042"
+ }
+ lpa {
+ connection: "localhost:9042"
+ }
+}
+neo4j_objecttypes_enabled=["Question"]
+
+import {
+ request_size_limit = 200
+ output_topic_name = "local.auto.creation.job.request"
+ required_props {
+ question = ["name", "code", "mimeType", "framework", "channel"]
+ questionseet = ["name", "code", "mimeType", "framework", "channel"]
+ }
+ remove_props {
+ question = []
+ questionseet = []
+ }
+}
\ No newline at end of file
diff --git a/learning-api/content-service/conf/logback.xml b/assessment-api/assessment-service/conf/logback.xml
similarity index 100%
rename from learning-api/content-service/conf/logback.xml
rename to assessment-api/assessment-service/conf/logback.xml
diff --git a/assessment-api/assessment-service/conf/routes b/assessment-api/assessment-service/conf/routes
new file mode 100644
index 000000000..e1fd74252
--- /dev/null
+++ b/assessment-api/assessment-service/conf/routes
@@ -0,0 +1,38 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+GET /health controllers.HealthController.health
+GET /service/health controllers.HealthController.serviceHealth
+
+# ItemSet API's
+POST /itemset/v3/create controllers.v3.ItemSetController.create
+GET /itemset/v3/read/:identifier controllers.v3.ItemSetController.read(identifier:String, fields:Option[String])
+PATCH /itemset/v3/update/:identifier controllers.v3.ItemSetController.update(identifier:String)
+POST /itemset/v3/review/:identifier controllers.v3.ItemSetController.review(identifier:String)
+DELETE /itemset/v3/retire/:identifier controllers.v3.ItemSetController.retire(identifier:String)
+
+# Question API's
+POST /question/v4/create controllers.v4.QuestionController.create
+GET /question/v4/read/:identifier controllers.v4.QuestionController.read(identifier:String, mode:Option[String], fields:Option[String])
+PATCH /question/v4/update/:identifier controllers.v4.QuestionController.update(identifier:String)
+POST /question/v4/review/:identifier controllers.v4.QuestionController.review(identifier:String)
+POST /question/v4/publish/:identifier controllers.v4.QuestionController.publish(identifier:String)
+DELETE /question/v4/retire/:identifier controllers.v4.QuestionController.retire(identifier:String)
+POST /question/v4/import controllers.v4.QuestionController.importQuestion()
+PATCH /question/v4/system/update/:identifier controllers.v4.QuestionController.systemUpdate(identifier:String)
+POST /question/v4/list controllers.v4.QuestionController.list(fields:Option[String])
+
+# QuestionSet API's
+POST /questionset/v4/create controllers.v4.QuestionSetController.create
+GET /questionset/v4/read/:identifier controllers.v4.QuestionSetController.read(identifier:String, mode:Option[String], fields:Option[String])
+PATCH /questionset/v4/update/:identifier controllers.v4.QuestionSetController.update(identifier:String)
+POST /questionset/v4/review/:identifier controllers.v4.QuestionSetController.review(identifier:String)
+POST /questionset/v4/publish/:identifier controllers.v4.QuestionSetController.publish(identifier:String)
+DELETE /questionset/v4/retire/:identifier controllers.v4.QuestionSetController.retire(identifier:String)
+PATCH /questionset/v4/add controllers.v4.QuestionSetController.add
+DELETE /questionset/v4/remove controllers.v4.QuestionSetController.remove
+PATCH /questionset/v4/hierarchy/update controllers.v4.QuestionSetController.updateHierarchy
+GET /questionset/v4/hierarchy/:identifier controllers.v4.QuestionSetController.getHierarchy(identifier:String, mode:Option[String])
+POST /questionset/v4/reject/:identifier controllers.v4.QuestionSetController.reject(identifier:String)
+POST /questionset/v4/import controllers.v4.QuestionSetController.importQuestionSet()
+PATCH /questionset/v4/system/update/:identifier controllers.v4.QuestionSetController.systemUpdate(identifier:String)
\ No newline at end of file
diff --git a/assessment-api/assessment-service/pom.xml b/assessment-api/assessment-service/pom.xml
new file mode 100644
index 000000000..620262701
--- /dev/null
+++ b/assessment-api/assessment-service/pom.xml
@@ -0,0 +1,144 @@
+
+
+
+ assessment-api
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ assessment-service
+ play2
+
+
+
+ scalaz-bintray
+ Scalaz Bintray - releases
+ https://dl.bintray.com/scalaz/releases/
+
+ false
+
+
+
+
+
+ typesafe-releases-plugins
+ https://repo.typesafe.com/typesafe/releases/
+
+ false
+
+
+
+
+ 2.7.2
+ 1.0.0-rc5
+ 1.0.0
+
+
+
+
+ com.typesafe.play
+ play_${scala.major.version}
+ ${play2.version}
+
+
+ com.typesafe.play
+ play-guice_${scala.major.version}
+ ${play2.version}
+
+
+ com.typesafe.play
+ filters-helpers_${scala.major.version}
+ ${play2.version}
+
+
+ com.typesafe.play
+ play-logback_${scala.major.version}
+ ${play2.version}
+ runtime
+
+
+ com.typesafe.play
+ play-netty-server_${scala.major.version}
+ ${play2.version}
+ runtime
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.sunbird
+ assessment-actors
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ 3.1.2
+ test
+
+
+ com.typesafe.play
+ play-specs2_${scala.maj.version}
+ ${play2.version}
+ test
+
+
+ org.joda
+ joda-convert
+ 2.2.1
+
+
+
+ ${basedir}/app
+ ${basedir}/test
+
+
+ ${basedir}/conf
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M4
+
+
+ **/*Spec.java
+ **/*Test.java
+
+
+
+
+ com.google.code.play2-maven-plugin
+ play2-maven-plugin
+ ${play2.plugin.version}
+ true
+
+
+ com.google.code.sbt-compiler-maven-plugin
+ sbt-compiler-maven-plugin
+ ${sbt-compiler.plugin.version}
+
+ -feature -deprecation -Xfatal-warnings
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+ .*RoutesPrefix.*;.*Routes.*;.*javascript.*
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assessment-api/assessment-service/test/controllers/base/BaseSpec.scala b/assessment-api/assessment-service/test/controllers/base/BaseSpec.scala
new file mode 100644
index 000000000..883693c86
--- /dev/null
+++ b/assessment-api/assessment-service/test/controllers/base/BaseSpec.scala
@@ -0,0 +1,38 @@
+package controllers.base
+
+import com.typesafe.config.ConfigFactory
+import modules.TestModule
+import org.specs2.mutable.Specification
+import play.api.inject.guice.GuiceApplicationBuilder
+import play.api.libs.json.Json
+import play.api.mvc.Result
+import play.api.test.Helpers.{POST, contentAsString, contentType, defaultAwaitTimeout, route, status, _}
+import play.api.test.{FakeHeaders, FakeRequest}
+
+import scala.concurrent.Future
+
+class BaseSpec extends Specification {
+ implicit val app = new GuiceApplicationBuilder()
+ .disable(classOf[modules.AssessmentModule])
+ .bindings(new TestModule)
+ .build
+ implicit val config = ConfigFactory.load()
+
+ def post(apiURL: String, request: String, h: FakeHeaders = FakeHeaders(Seq()))
+ : Future[Result] = {
+ val headers = h.add(("content-type", "application/json"))
+ route(app, FakeRequest(POST, apiURL, headers, Json.toJson(Json.parse(request)))).get
+ }
+
+ def isOK(response: Future[Result]) {
+ status(response) must equalTo(OK)
+ contentType(response) must beSome.which(_ == "application/json")
+ contentAsString(response) must contain(""""status":"successful"""")
+ }
+
+ def hasClientError(response: Future[Result]) {
+ status(response) must equalTo(BAD_REQUEST)
+ contentType(response) must beSome.which(_ == "application/json")
+ contentAsString(response) must contain(""""err":"CLIENT_ERROR","status":"failed"""")
+ }
+}
diff --git a/assessment-api/assessment-service/test/controllers/v3/HealthControllerSpec.scala b/assessment-api/assessment-service/test/controllers/v3/HealthControllerSpec.scala
new file mode 100644
index 000000000..49f351a62
--- /dev/null
+++ b/assessment-api/assessment-service/test/controllers/v3/HealthControllerSpec.scala
@@ -0,0 +1,18 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.{FakeRequest, Helpers}
+import play.api.test.Helpers.{OK, status}
+
+@RunWith(classOf[JUnitRunner])
+class HealthControllerSpec extends BaseSpec {
+
+ "return api health status report - successful response" in {
+ val controller = app.injector.instanceOf[controllers.HealthController]
+ val result = controller.health()(FakeRequest())
+ isOK(result)
+ status(result)(Helpers.defaultAwaitTimeout) must equalTo(OK)
+ }
+}
diff --git a/assessment-api/assessment-service/test/controllers/v3/ItemSetControllerSpec.scala b/assessment-api/assessment-service/test/controllers/v3/ItemSetControllerSpec.scala
new file mode 100644
index 000000000..c0a7cd546
--- /dev/null
+++ b/assessment-api/assessment-service/test/controllers/v3/ItemSetControllerSpec.scala
@@ -0,0 +1,43 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, defaultAwaitTimeout, status}
+
+@RunWith(classOf[JUnitRunner])
+class ItemSetControllerSpec extends BaseSpec {
+
+ val controller = app.injector.instanceOf[controllers.v3.ItemSetController]
+
+ "create should create an itemset successfully for given valid request" in {
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "read should return an itemset successfully for given valid identifier" in {
+ val result = controller.read("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "update should update the itemset successfully for given valid identifier" in {
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "review should update the itemset status to Review successfully for given valid identifier" in {
+ val result = controller.review("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "retire should update the itemset status to Retired successfully for given valid identifier" in {
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+}
diff --git a/assessment-api/assessment-service/test/controllers/v4/QuestionControllerSpec.scala b/assessment-api/assessment-service/test/controllers/v4/QuestionControllerSpec.scala
new file mode 100644
index 000000000..94e8d4329
--- /dev/null
+++ b/assessment-api/assessment-service/test/controllers/v4/QuestionControllerSpec.scala
@@ -0,0 +1,67 @@
+package controllers.v4
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, defaultAwaitTimeout, status}
+
+@RunWith(classOf[JUnitRunner])
+class QuestionControllerSpec extends BaseSpec {
+
+ val controller = app.injector.instanceOf[controllers.v4.QuestionController]
+
+ "create should create an question successfully for given valid request" in {
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "read should return an question successfully for given valid identifier" in {
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "update should update the question successfully for given valid identifier" in {
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "review should update the question status to Review successfully for given valid identifier" in {
+ val result = controller.review("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "publish should update the question status to Live successfully for given valid identifier" in {
+ val result = controller.publish("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "retire should update the question status to Retired successfully for given valid identifier" in {
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "import should create a question successfully for given valid request" in {
+ val result = controller.importQuestion()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "systemUpdate should update an question successfully for given valid request" in {
+ val result = controller.systemUpdate("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "list should list all the questions for given list of ids in the request" in {
+ val result = controller.list(None)(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+}
diff --git a/assessment-api/assessment-service/test/controllers/v4/QuestionSetControllerSpec.scala b/assessment-api/assessment-service/test/controllers/v4/QuestionSetControllerSpec.scala
new file mode 100644
index 000000000..23d12518a
--- /dev/null
+++ b/assessment-api/assessment-service/test/controllers/v4/QuestionSetControllerSpec.scala
@@ -0,0 +1,93 @@
+package controllers.v4
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, defaultAwaitTimeout, status}
+
+@RunWith(classOf[JUnitRunner])
+class QuestionSetControllerSpec extends BaseSpec {
+
+ val controller = app.injector.instanceOf[controllers.v4.QuestionSetController]
+
+ "create should create an questionSet successfully for given valid request" in {
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "read should return an questionSet successfully for given valid identifier" in {
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "update should update the questionSet successfully for given valid identifier" in {
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "review should update the questionSet status to Review successfully for given valid identifier" in {
+ val result = controller.review("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "publish should update the questionSet status to Live successfully for given valid identifier" in {
+ val result = controller.publish("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "retire should update the questionSet status to Retired successfully for given valid identifier" in {
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "update Hierarchy should update hierarchy successfully for given valid identifier" in {
+ val result = controller.updateHierarchy()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "read Hierarchy should read Hierarchy successfully for given valid identifier" in {
+ val result = controller.getHierarchy("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+
+ "add question should update the questionSet status to Add question successfully for given valid identifier" in {
+ val result = controller.add()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+
+ "remove question should update the questionSet status to remove question successfully for given valid identifier" in {
+ val result = controller.remove()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "review should update the questionSet status to Reject successfully for given valid identifier" in {
+ val result = controller.reject("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "import should create an questionSet successfully for given valid request" in {
+ val result = controller.importQuestionSet()(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+
+ "systemUpdate should update an questionSet successfully for given valid request" in {
+ val result = controller.systemUpdate("do_123")(FakeRequest())
+ isOK(result)
+ status(result)(defaultAwaitTimeout) must equalTo(OK)
+ }
+}
diff --git a/assessment-api/assessment-service/test/modules/TestModule.scala b/assessment-api/assessment-service/test/modules/TestModule.scala
new file mode 100644
index 000000000..8867e7cbc
--- /dev/null
+++ b/assessment-api/assessment-service/test/modules/TestModule.scala
@@ -0,0 +1,28 @@
+package modules
+
+import com.google.inject.AbstractModule
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import play.libs.akka.AkkaGuiceSupport
+import utils.ActorNames
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class TestModule extends AbstractModule with AkkaGuiceSupport {
+ override def configure(): Unit = {
+ bindActor(classOf[TestActor], ActorNames.HEALTH_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.ITEM_SET_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.QUESTION_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.QUESTION_SET_ACTOR)
+ println("Test Module is initialized...")
+ }
+}
+
+class TestActor extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ Future(ResponseHandler.OK)
+ }
+}
diff --git a/assessment-api/pom.xml b/assessment-api/pom.xml
new file mode 100644
index 000000000..b1bb913ed
--- /dev/null
+++ b/assessment-api/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ knowledge-platform
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ assessment-api
+ pom
+ assessment-api
+
+ assessment-actors
+ assessment-service
+ qs-hierarchy-manager
+
+
+
+ UTF-8
+ UTF-8
+ 2.11
+
+
+
+
+
+
+ maven-assembly-plugin
+ 3.3.0
+
+
+ src/assembly/bin.xml
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 11
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+
+ ${scala.version}
+ true
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assessment-api/qs-hierarchy-manager/pom.xml b/assessment-api/qs-hierarchy-manager/pom.xml
new file mode 100644
index 000000000..e4de07f22
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ assessment-api
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ qs-hierarchy-manager
+ 1.0-SNAPSHOT
+
+
+
+ org.sunbird
+ graph-engine_2.11
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.sunbird
+ platform-common
+ 1.0-SNAPSHOT
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ 3.1.2
+ test
+
+
+ org.neo4j
+ neo4j-bolt
+ 3.5.0
+ test
+
+
+ org.neo4j
+ neo4j-graphdb-api
+ 3.5.0
+ test
+
+
+ org.neo4j
+ neo4j
+ 3.5.0
+ test
+
+
+ org.cassandraunit
+ cassandra-unit
+ 3.11.2.0
+ test
+
+
+ httpcore
+ org.apache.httpcomponents
+
+
+ httpclient
+ org.apache.httpcomponents
+
+
+
+
+ com.mashape.unirest
+ unirest-java
+ 1.4.9
+
+
+
+
+
+ src/main/scala
+ src/test/scala
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.4.0
+
+ ${scala.version}
+ false
+
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+ scala-test-compile
+ process-test-resources
+
+ testCompile
+
+
+
+
+
+ org.scalatest
+ scalatest-maven-plugin
+ 2.0.0
+
+
+ test
+ test
+
+ test
+
+
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
new file mode 100644
index 000000000..6b3c8f8aa
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
@@ -0,0 +1,601 @@
+package org.sunbird.managers
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException}
+import org.sunbird.common.{JsonUtils, Platform}
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+import scala.collection.JavaConverters
+import scala.concurrent.{ExecutionContext, Future}
+import com.mashape.unirest.http.HttpResponse
+import com.mashape.unirest.http.Unirest
+import org.apache.commons.collections4.{CollectionUtils, MapUtils}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.utils.{HierarchyConstants}
+
+object HierarchyManager {
+
+ val schemaName: String = "questionset"
+ val schemaVersion: String = "1.0"
+ val imgSuffix: String = ".img"
+ val hierarchyPrefix: String = "qs_hierarchy_"
+ val statusList = List("Live", "Unlisted", "Flagged")
+ val ASSESSMENT_OBJECT_TYPES = List("Question", "QuestionSet")
+
+ val keyTobeRemoved = {
+ if(Platform.config.hasPath("content.hierarchy.removed_props_for_leafNodes"))
+ Platform.config.getStringList("content.hierarchy.removed_props_for_leafNodes")
+ else
+ java.util.Arrays.asList("collections","children","usedByContent","item_sets","methods","libraries","editorState")
+ }
+
+ @throws[Exception]
+ def addLeafNodesToHierarchy(request:Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateRequest(request)
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ val unitId = request.getRequest.getOrDefault("collectionId", "").asInstanceOf[String]
+ if (StringUtils.isBlank(unitId)) attachLeafToRootNode(request, rootNode, "add") else {
+ val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes", "originData"), schemaName, schemaVersion)
+ if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
+ Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "collectionId " + unitId + " does not exist")}
+ }else {
+ val hierarchyFuture = fetchHierarchy(request, rootNode.getIdentifier)
+ hierarchyFuture.map(hierarchy => {
+ if(hierarchy.isEmpty){
+ Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
+ } else {
+ val leafNodesFuture = fetchLeafNodes(request)
+ leafNodesFuture.map(leafNodes => {
+ updateRootNode(rootNode, request, "add").map(node => {
+ val updateResponse = updateHierarchy(unitId, hierarchy, leafNodes, node, request, "add")
+ updateResponse.map(response => {
+ if(!ResponseHandler.checkError(response)) {
+ ResponseHandler.OK
+ .put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))
+ .put(unitId, Map("children" -> request.get("children")).asJava)
+ }else {
+ response
+ }
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+ }
+ }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
+ }
+
+ @throws[Exception]
+ def removeLeafNodesFromHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateRequest(request)
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ val unitId = request.getRequest.getOrDefault("collectionId", "").asInstanceOf[String]
+ if (StringUtils.isBlank(unitId)) attachLeafToRootNode(request, rootNode, "remove") else {
+ val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes", "originData"), schemaName, schemaVersion)
+ if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
+ Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "collectionId " + unitId + " does not exist")}
+ }else {
+ val hierarchyFuture = fetchHierarchy(request, rootNode.getIdentifier)
+ hierarchyFuture.map(hierarchy => {
+ if(hierarchy.isEmpty){
+ Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
+ } else {
+ updateRootNode(rootNode, request, "remove").map(node =>{
+ val updateResponse = updateHierarchy(unitId, hierarchy, null, node, request, "remove")
+ updateResponse.map(response => {
+ if(!ResponseHandler.checkError(response)) {
+ ResponseHandler.OK.put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))
+ } else {
+ response
+ }
+ })
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+ }
+ }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
+ }
+
+ def attachLeafToRootNode(request: Request, rootNode: Node, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ fetchHierarchy(request, rootNode.getIdentifier).map(hierarchy => {
+ if (hierarchy.isEmpty) {
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty"))
+ } else {
+ fetchLeafNodes(request).map(leafNodes => {
+ updateRootNode(rootNode, request, operation).map(node => {
+ updateRootHierarchy(hierarchy, leafNodes, node, request, operation).map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ ResponseHandler.OK
+ .put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))
+ .put("children", request.get("children"))
+ } else response
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+
+ def updateRootHierarchy(hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val leafNodeIds = request.get("children").asInstanceOf[util.List[String]]
+ val req = new Request(request)
+ if ("add".equalsIgnoreCase(operation)) {
+ val updatedChildren = restructureUnit(hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]],
+ convertNodeToMap(leafNodes), leafNodeIds, 1, rootNode.getIdentifier.replace(".img", ""))
+ val updatedHierarchy = Map("children" -> updatedChildren, "identifier" -> rootNode.getIdentifier.replace(".img", "")).asJava
+ req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy))
+ }
+ if ("remove".equalsIgnoreCase(operation)) {
+ val filteredChildren = hierarchy.get("children")
+ .asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]].asScala
+ .filter(child => !leafNodeIds.contains(child.get("identifier")))
+ filteredChildren.sortBy(_.get("index").asInstanceOf[Integer])
+ .zipWithIndex.foreach(zippedChild => zippedChild._1.put("index", (zippedChild._2.asInstanceOf[Integer] + 1).asInstanceOf[Object]))
+ val updatedHierarchy = Map("children" -> filteredChildren, "identifier" -> rootNode.getIdentifier.replace(".img", "")).asJava
+ req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy))
+ }
+ req.put("identifier", rootNode.getIdentifier)
+ oec.graphService.saveExternalProps(req)
+ }
+
+ @throws[Exception]
+ def getHierarchy(request : Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val mode = request.get("mode").asInstanceOf[String]
+ if(StringUtils.isNotEmpty(mode) && mode.equals("edit"))
+ getUnPublishedHierarchy(request)
+ else
+ getPublishedHierarchy(request)
+ }
+
+ @throws[Exception]
+ def getUnPublishedHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ if (StringUtils.equalsIgnoreCase("Retired", rootNode.getMetadata.getOrDefault("status", "").asInstanceOf[String])) {
+ Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist"))
+ }
+ val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
+ var metadata: util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ val hierarchy = fetchHierarchy(request, rootNode.getIdentifier)
+ hierarchy.map(hierarchy => {
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]]
+ val leafNodeIds = new util.ArrayList[String]()
+ fetchAllLeafNodes(children, leafNodeIds)
+ getLatestLeafNodes(leafNodeIds).map(leafNodesMap => {
+ updateLatestLeafNodes(children, leafNodesMap)
+ metadata.put("children", children)
+ metadata.put("identifier", request.get("rootId"))
+ if(StringUtils.isNotEmpty(bookmarkId))
+ metadata = filterBookmarkHierarchy(metadata.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]], bookmarkId)
+ if (MapUtils.isEmpty(metadata)) {
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
+ } else {
+ ResponseHandler.OK.put("questionSet", metadata)
+ }
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f) recoverWith { case e: ResourceNotFoundException => {
+ val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String])
+ searchResponse.map(rootHierarchy => {
+ if(!rootHierarchy.isEmpty && StringUtils.isNotEmpty(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])){
+ val unPublishedBookmarkHierarchy = getUnpublishedBookmarkHierarchy(request, rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])
+ unPublishedBookmarkHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]]
+ val leafNodeIds = new util.ArrayList[String]()
+ fetchAllLeafNodes(children, leafNodeIds)
+ getLatestLeafNodes(leafNodeIds).map(leafNodesMap => {
+ updateLatestLeafNodes(children, leafNodesMap)
+ hierarchy.put("children", children)
+ })
+ ResponseHandler.OK.put("questionSet", hierarchy)
+ } else
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")
+ })
+ } else {
+ Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist"))
+ }
+ }).flatMap(f => f)
+ }
+ }
+ }
+
+ @throws[Exception]
+ def getPublishedHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ val redisHierarchy = if(Platform.getBoolean("questionset.cache.enable", false)) RedisCache.get(hierarchyPrefix + request.get("rootId")) else ""
+
+ val hierarchyFuture = if (StringUtils.isNotEmpty(redisHierarchy)) {
+ Future(mapAsJavaMap(Map("questionSet" -> JsonUtils.deserialize(redisHierarchy, classOf[java.util.Map[String, AnyRef]]))))
+ } else getCassandraHierarchy(request)
+ hierarchyFuture.map(result => {
+ if (!result.isEmpty) {
+ val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
+ val rootHierarchy = result.get("questionSet").asInstanceOf[util.Map[String, AnyRef]]
+ if (StringUtils.isEmpty(bookmarkId)) {
+ ResponseHandler.OK.put("questionSet", rootHierarchy)
+ } else {
+ val children = rootHierarchy.getOrElse("children", new util.ArrayList[util.Map[String, AnyRef]]()).asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ val bookmarkHierarchy = filterBookmarkHierarchy(children, bookmarkId)
+ if (MapUtils.isEmpty(bookmarkHierarchy)) {
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
+ } else {
+ ResponseHandler.OK.put("questionSet", bookmarkHierarchy)
+ }
+ }
+ } else
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")
+ })
+ }
+
+ def validateRequest(request: Request)(implicit ec: ExecutionContext) = {
+ val rootId = request.get("rootId").asInstanceOf[String]
+ val children = request.get("children").asInstanceOf[java.util.List[String]]
+ if (StringUtils.isBlank(rootId)) {
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "rootId is mandatory")
+ }
+ if (null == children || children.isEmpty) {
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "children are mandatory")
+ }
+ }
+
+ private def getRootNode(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val req = new Request(request)
+ req.put("identifier", request.get("rootId").asInstanceOf[String])
+ req.put("mode", request.get("mode").asInstanceOf[String])
+ req.put("fields",request.get("fields").asInstanceOf[java.util.List[String]])
+ DataNode.read(req)
+ }
+
+ def fetchLeafNodes(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
+ val req = new Request(request)
+ req.put("identifiers", leafNodes)
+ DataNode.list(req).map(nodes => {
+ if(nodes.size() != leafNodes.size()) {
+ val filteredList = leafNodes.toList.filter(id => !nodes.contains(id))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Children which are not available are: " + filteredList)
+ } else {
+ val invalidNodes = nodes.filterNot(node => ASSESSMENT_OBJECT_TYPES.contains(node.getObjectType))
+ if (CollectionUtils.isNotEmpty(invalidNodes))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), s"Children must be of types $ASSESSMENT_OBJECT_TYPES for ids: ${invalidNodes.map(_.getIdentifier)}")
+ else nodes.toList
+ }
+ })
+ }
+
+ def convertNodeToMap(leafNodes: List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): java.util.List[java.util.Map[String, AnyRef]] = {
+ leafNodes.map(node => {
+ val nodeMap:java.util.Map[String,AnyRef] = NodeUtil.serialize(node, null, node.getObjectType.toLowerCase().replace("image", ""), schemaVersion)
+ nodeMap.keySet().removeAll(keyTobeRemoved)
+ nodeMap
+ })
+ }
+
+ def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String]): Unit = {
+ val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
+ if(null != childNodes && !childNodes.isEmpty){
+ val child = childNodes.get(0)
+ val childList = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]]
+ val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, (child.get("depth").asInstanceOf[Integer] + 1), unitId)
+ child.put("children", restructuredChildren)
+ } else {
+ for(child <- children) {
+ if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
+ addChildrenToUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodes, leafNodeIds)
+ }
+ }
+ }
+
+ def removeChildrenFromUnit(children: java.util.List[java.util.Map[String, AnyRef]], unitId: String, leafNodeIds: java.util.List[String]):Unit = {
+ val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
+ if(null != childNodes && !childNodes.isEmpty){
+ val child = childNodes.get(0)
+ if(null != child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) {
+ var filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].filter(existingLeafNode => {
+ !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
+ })
+ var index: Integer = 1
+ filteredLeafNodes.toList.sortBy(x => x.get("index").asInstanceOf[Integer]).foreach(node => {
+ node.put("index", index)
+ index += 1
+ })
+ child.put("children", filteredLeafNodes)
+ }
+ } else {
+ for(child <- children) {
+ if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
+ removeChildrenFromUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodeIds)
+ }
+ }
+ }
+
+ def updateRootNode(rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val req = new Request(request)
+ val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
+ var childNodes = new java.util.ArrayList[String]()
+ childNodes.addAll(rootNode.getMetadata.getOrDefault("childNodes", Array[String]()).asInstanceOf[Array[String]].toList)
+ if(operation.equalsIgnoreCase("add"))
+ childNodes.addAll(leafNodes)
+ if(operation.equalsIgnoreCase("remove"))
+ childNodes.removeAll(leafNodes)
+ req.put("childNodes", childNodes.distinct.toArray)
+ req.getContext.put("identifier", rootNode.getIdentifier.replaceAll(imgSuffix, ""))
+ req.getContext.put("skipValidation", java.lang.Boolean.TRUE)
+ DataNode.update(req)
+ }
+
+ def updateHierarchy(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]]
+ if("add".equalsIgnoreCase(operation)){
+ val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes)
+ addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds)
+ }
+ if("remove".equalsIgnoreCase(operation)) {
+ removeChildrenFromUnit(children,unitId, leafNodeIds)
+ }
+ val rootId = rootNode.getIdentifier.replaceAll(imgSuffix, "")
+ val updatedHierarchy = new java.util.HashMap[String, AnyRef]()
+ updatedHierarchy.put("identifier", rootId)
+ updatedHierarchy.put("children", children)
+ val req = new Request(request)
+ req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy))
+ req.put("identifier", rootNode.getIdentifier)
+ oec.graphService.saveExternalProps(req)
+ }
+
+ def restructureUnit(childList: java.util.List[java.util.Map[String, AnyRef]], leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], depth: Integer, parent: String): java.util.List[java.util.Map[String, AnyRef]] = {
+ var maxIndex:Integer = 0
+ var leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]()
+ for(leafNode <- leafNodes){
+ leafNodeMap.put(leafNode.get("identifier").asInstanceOf[String], JavaConverters.mapAsJavaMapConverter(leafNode).asJava)
+ }
+ var filteredLeafNodes: java.util.List[java.util.Map[String, AnyRef]] = new util.ArrayList[java.util.Map[String, AnyRef]]()
+ if(null != childList && !childList.isEmpty) {
+ val childMap:Map[String, java.util.Map[String, AnyRef]] = childList.toList.map(f => f.get("identifier").asInstanceOf[String] -> f).toMap
+ val existingLeafNodes = childMap.filter(p => leafNodeIds.contains(p._1))
+ existingLeafNodes.map(en => {
+ leafNodeMap.get(en._1).put("index", en._2.get("index").asInstanceOf[Integer])
+ })
+ filteredLeafNodes = bufferAsJavaList(childList.filter(existingLeafNode => {
+ !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
+ }))
+ maxIndex = childMap.values.toList.map(child => child.get("index").asInstanceOf[Integer]).toList.max.asInstanceOf[Integer]
+ }
+ leafNodeIds.foreach(id => {
+ var node = leafNodeMap.getOrDefault(id, new util.HashMap[String, AnyRef]())
+ node.put("parent", parent)
+ node.put("depth", depth)
+ if( null == node.get("index")) {
+ val index:Integer = maxIndex + 1
+ node.put("index", index)
+ maxIndex += 1
+ }
+ filteredLeafNodes.add(node)
+ })
+ filteredLeafNodes
+ }
+
+ def fetchHierarchy(request: Request, identifier: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Map[String, AnyRef]] = {
+ val req = new Request(request)
+ req.put("identifier", identifier)
+ val responseFuture = oec.graphService.readExternalProps(req, List("hierarchy"))
+ responseFuture.map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ val hierarchyString = response.getResult.toMap.getOrDefault("hierarchy", "").asInstanceOf[String]
+ if (StringUtils.isNotEmpty(hierarchyString)) {
+ Future(JsonUtils.deserialize(hierarchyString, classOf[java.util.Map[String, AnyRef]]).toMap)
+ } else
+ Future(Map[String, AnyRef]())
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404 && Platform.config.hasPath("collection.image.migration.enabled") && Platform.config.getBoolean("collection.image.migration.enabled")) {
+ req.put("identifier", identifier.replaceAll(".img", "") + ".img")
+ val responseFuture = oec.graphService.readExternalProps(req, List("hierarchy"))
+ responseFuture.map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ val hierarchyString = response.getResult.toMap.getOrDefault("hierarchy", "").asInstanceOf[String]
+ if (StringUtils.isNotEmpty(hierarchyString)) {
+ JsonUtils.deserialize(hierarchyString, classOf[java.util.Map[String, AnyRef]]).toMap
+ } else
+ Map[String, AnyRef]()
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404)
+ Map[String, AnyRef]()
+ else
+ throw new ServerException("ERR_WHILE_FETCHING_HIERARCHY_FROM_CASSANDRA", "Error while fetching hierarchy from cassandra")
+ })
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404)
+ Future(Map[String, AnyRef]())
+ else
+ throw new ServerException("ERR_WHILE_FETCHING_HIERARCHY_FROM_CASSANDRA", "Error while fetching hierarchy from cassandra")
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def getCassandraHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[util.Map[String, AnyRef]] = {
+ val rootHierarchy: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]()
+ val hierarchy = fetchHierarchy(request, request.getRequest.get("rootId").asInstanceOf[String])
+ hierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ if (StringUtils.isNotEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String])) {
+ val hierarchyMap = mapAsJavaMap(hierarchy)
+ rootHierarchy.put("questionSet", hierarchyMap)
+ RedisCache.set(hierarchyPrefix + request.get("rootId"), JsonUtils.serialize(hierarchyMap))
+ Future(rootHierarchy)
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String])
+ searchResponse.map(response => {
+ if (!response.isEmpty) {
+ if (StringUtils.isNotEmpty(response.getOrDefault("identifier", "").asInstanceOf[String])) {
+ val parentHierarchy = fetchHierarchy(request, response.get("identifier").asInstanceOf[String])
+ parentHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ if (StringUtils.isNoneEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.HashMap[String, AnyRef]]])) {
+ val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String])
+ if (!bookmarkHierarchy.isEmpty) {
+ rootHierarchy.put("questionSet", hierarchy)
+ RedisCache.set(hierarchyPrefix + request.get("rootId"), JsonUtils.serialize(hierarchy))
+ rootHierarchy
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ })
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def searchRootIdInElasticSearch(rootId: String)(implicit ec: ExecutionContext): Future[util.Map[String, AnyRef]] = {
+ val mapper: ObjectMapper = new ObjectMapper()
+ val searchRequest: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]() {
+ put("request", new util.HashMap[String, AnyRef]() {
+ put("filters", new util.HashMap[String, AnyRef]() {
+ put("status", new util.ArrayList[String]() {
+ add("Live");
+ add("Unlisted")
+ })
+ put("mimeType", "application/vnd.ekstep.content-collection")
+ put("childNodes", new util.ArrayList[String]() {
+ add(rootId)
+ })
+ put("visibility", "Default")
+ })
+ put("fields", new util.ArrayList[String]() {
+ add("identifier")
+ })
+ })
+ }
+ val url: String = if (Platform.config.hasPath("composite.search.url")) Platform.config.getString("composite.search.url") else "https://dev.sunbirded.org/action/composite/v3/search"
+ val httpResponse: HttpResponse[String] = Unirest.post(url).header("Content-Type", "application/json").body(mapper.writeValueAsString(searchRequest)).asString
+ if (httpResponse.getStatus == 200) {
+ val response: Response = JsonUtils.deserialize(httpResponse.getBody, classOf[Response])
+ if (response.get("count").asInstanceOf[Integer] > 0 && CollectionUtils.isNotEmpty(response.get("content").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) {
+ Future(response.get("content").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]].get(0))
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ throw new ServerException("SERVER_ERROR", "Invalid response from search")
+ }
+ }
+
+ def filterBookmarkHierarchy(children: util.List[util.Map[String, AnyRef]], bookmarkId: String)(implicit ec: ExecutionContext): util.Map[String, AnyRef] = {
+ if (CollectionUtils.isNotEmpty(children)) {
+ val response = children.filter(_.get("identifier") == bookmarkId).toList
+ if (CollectionUtils.isNotEmpty(response)) {
+ response.get(0)
+ } else {
+ val nextChildren = bufferAsJavaList(children.flatMap(child => {
+ if (!child.isEmpty && CollectionUtils.isNotEmpty(child.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]))
+ child.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ else new util.ArrayList[util.Map[String, AnyRef]]
+ }))
+ filterBookmarkHierarchy(nextChildren, bookmarkId)
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ }
+
+ def getUnpublishedBookmarkHierarchy(request: Request, identifier: String)(implicit ec: ExecutionContext, oec:OntologyEngineContext): Future[util.Map[String, AnyRef]] = {
+ if (StringUtils.isNotEmpty(identifier)) {
+ val parentHierarchy = fetchHierarchy(request, identifier + imgSuffix)
+ parentHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) {
+ val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String])
+ if (!bookmarkHierarchy.isEmpty) {
+ bookmarkHierarchy
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ })
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ }
+
+ def updateLatestLeafNodes(children: util.List[util.Map[String, AnyRef]], leafNodeMap: util.Map[String, AnyRef]): List[Any] = {
+ children.toList.map(content => {
+ if(StringUtils.equalsIgnoreCase("Default", content.getOrDefault("visibility", "").asInstanceOf[String])) {
+ val metadata: util.Map[String, AnyRef] = leafNodeMap.getOrDefault(content.get("identifier").asInstanceOf[String], new java.util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]]
+ if(HierarchyConstants.RETIRED_STATUS.equalsIgnoreCase(metadata.getOrDefault("status", HierarchyConstants.RETIRED_STATUS).asInstanceOf[String])){
+ children.remove(content)
+ } else {
+ content.putAll(metadata)
+ }
+ } else {
+ updateLatestLeafNodes(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], leafNodeMap)
+ }
+ })
+ }
+
+ def fetchAllLeafNodes(children: util.List[util.Map[String, AnyRef]], leafNodeIds: util.List[String]): List[Any] = {
+ children.toList.map(content => {
+ if(StringUtils.equalsIgnoreCase("Default", content.getOrDefault("visibility", "").asInstanceOf[String])) {
+ leafNodeIds.add(content.get("identifier").asInstanceOf[String])
+ leafNodeIds
+ } else {
+ fetchAllLeafNodes(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], leafNodeIds)
+ }
+ })
+ }
+
+ def getLatestLeafNodes(leafNodeIds : util.List[String])(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ if(CollectionUtils.isNotEmpty(leafNodeIds)) {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.GRAPH_ID, HierarchyConstants.TAXONOMY_ID)
+ }
+ })
+ request.put("identifiers", leafNodeIds)
+ DataNode.list(request).map(nodes => {
+ val leafNodeMap: Map[String, AnyRef] = nodes.toList.map(node => (node.getIdentifier, NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true).asInstanceOf[AnyRef])).toMap
+ val imageNodeIds: util.List[String] = JavaConverters.seqAsJavaListConverter(leafNodeIds.toList.map(id => id + HierarchyConstants.IMAGE_SUFFIX)).asJava
+ request.put("identifiers", imageNodeIds)
+ DataNode.list(request).map(imageNodes => {
+ val imageLeafNodeMap: Map[String, AnyRef] = imageNodes.toList.map(imageNode => {
+ val identifier = imageNode.getIdentifier.replaceAll(HierarchyConstants.IMAGE_SUFFIX, "")
+ val metadata = NodeUtil.serialize(imageNode, null, imageNode.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true)
+ metadata.replace("identifier", identifier)
+ (identifier, metadata.asInstanceOf[AnyRef])
+ }).toMap
+ val updatedMap = leafNodeMap ++ imageLeafNodeMap
+ JavaConverters.mapAsJavaMapConverter(updatedMap).asJava
+ })
+ }).flatMap(f => f)
+ } else {
+ Future{new util.HashMap[String, AnyRef]()}
+ }
+ }
+
+}
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala
new file mode 100644
index 000000000..fb480c39f
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala
@@ -0,0 +1,513 @@
+package org.sunbird.managers
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import org.apache.commons.collections4.{CollectionUtils, MapUtils}
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ServerException}
+import org.sunbird.common.{DateUtils, JsonUtils, Platform}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.common.Identifier
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.external.ExternalPropsManager
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.schema.DefinitionNode
+import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.utils.{HierarchyConstants, HierarchyErrorCodes}
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+import scala.concurrent.{ExecutionContext, Future}
+
+object UpdateHierarchyManager {
+ val neo4jCreateTypes: java.util.List[String] = Platform.getStringList("neo4j_objecttypes_enabled", List("Question").asJava)
+
+ @throws[Exception]
+ def updateHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val (nodesModified, hierarchy) = validateRequest(request)
+ val rootId: String = getRootId(nodesModified, hierarchy)
+ request.getContext.put(HierarchyConstants.ROOT_ID, rootId)
+ getValidatedRootNode(rootId, request).map(node => {
+ getExistingHierarchy(request, node).map(existingHierarchy => {
+ val existingChildren = existingHierarchy.getOrElse(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.HashMap[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ val nodes = List(node)
+ addChildNodesInNodeList(existingChildren, request, nodes).map(list => (existingHierarchy, list))
+ }).flatMap(f => f)
+ .map(result => {
+ val nodes = result._2
+ TelemetryManager.info("NodeList final size: " + nodes.size)
+ val idMap: mutable.Map[String, String] = mutable.Map()
+ idMap += (rootId -> rootId)
+ updateNodesModifiedInNodeList(nodes, nodesModified, request, idMap).map(modifiedNodeList => {
+ getChildrenHierarchy(modifiedNodeList, rootId, hierarchy, idMap, result._1).map(children => {
+ TelemetryManager.log("Children for root id :" + rootId +" :: " + JsonUtils.serialize(children))
+ updateHierarchyData(rootId, children, modifiedNodeList, request).map(node => {
+ val response = ResponseHandler.OK()
+ response.put(HierarchyConstants.IDENTIFIER, rootId)
+ idMap.remove(rootId)
+ response.put(HierarchyConstants.IDENTIFIERS, mapAsJavaMap(idMap))
+ if (request.getContext.getOrDefault("shouldImageDelete", false.asInstanceOf[AnyRef]).asInstanceOf[Boolean])
+ deleteHierarchy(request)
+ Future(response)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ })
+ }).flatMap(f => f).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ private def validateRequest(request: Request)(implicit ec: ExecutionContext): (java.util.HashMap[String, AnyRef], java.util.HashMap[String, AnyRef]) = {
+ if (!request.getRequest.contains(HierarchyConstants.NODES_MODIFIED) && !request.getRequest.contains(HierarchyConstants.HIERARCHY))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Hierarchy data is empty")
+ val nodesModified: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.NODES_MODIFIED).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ val hierarchy: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.HIERARCHY).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ hierarchy.asScala.keys.foreach(key => {
+ if (StringUtils.equalsIgnoreCase(nodesModified.getOrDefault(key, new util.HashMap()).asInstanceOf[util.Map[String, AnyRef]]
+ .getOrDefault(HierarchyConstants.OBJECT_TYPE, "").asInstanceOf[String], "Question"))
+ throw new ClientException("ERR_QS_UPDATE_HIERARCHY", "Question cannot have children in hierarchy")
+ })
+ (nodesModified, hierarchy)
+ }
+
+ /**
+ * Checks if root id is empty, all black or image id
+ *
+ * @param nodesModified
+ * @param hierarchy
+ * @param ec
+ * @return
+ */
+ private def getRootId(nodesModified: java.util.HashMap[String, AnyRef], hierarchy: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext): String = {
+ val rootId: String = nodesModified.keySet()
+ .find(key => nodesModified.get(key).asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean])
+ .getOrElse(hierarchy.keySet().find(key => hierarchy.get(key).asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean]).orNull)
+ if (StringUtils.isEmpty(rootId) && StringUtils.isAllBlank(rootId) || StringUtils.contains(rootId, HierarchyConstants.IMAGE_SUFFIX))
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "Please Provide Valid Root Node Identifier")
+ rootId
+ }
+
+ private def getValidatedRootNode(identifier: String, request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val req = new Request(request)
+ req.put(HierarchyConstants.IDENTIFIER, identifier)
+ req.put(HierarchyConstants.MODE, HierarchyConstants.EDIT_MODE)
+ DataNode.read(req).map(rootNode => {
+ val metadata: java.util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new java.util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ if (!StringUtils.equals(metadata.get(HierarchyConstants.MIME_TYPE).asInstanceOf[String], HierarchyConstants.QUESTIONSET_MIME_TYPE)) {
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "Invalid MimeType for Root Node Identifier : " + identifier)
+ TelemetryManager.error("UpdateHierarchyManager.getValidatedRootNode :: Invalid MimeType for Root node id: " + identifier)
+ }
+ if(!StringUtils.equals(metadata.getOrDefault(HierarchyConstants.VISIBILITY, "").asInstanceOf[String], HierarchyConstants.DEFAULT)) {
+ TelemetryManager.error("UpdateHierarchyManager.getValidatedRootNode :: Invalid Visibility found for Root node id: " + identifier)
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "Invalid Visibility found for Root Node Identifier : " + identifier)
+ }
+ rootNode.setObjectType(HierarchyConstants.QUESTIONSET_OBJECT_TYPE)
+ rootNode.getMetadata.put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.QUESTIONSET_OBJECT_TYPE)
+ rootNode
+ })
+ }
+
+ private def getExistingHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[java.util.HashMap[String, AnyRef]] = {
+ fetchHierarchy(request, rootNode).map(hierarchyString => {
+ if (null != hierarchyString && !hierarchyString.asInstanceOf[String].isEmpty) {
+ JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.HashMap[String, AnyRef]])
+ } else new java.util.HashMap[String, AnyRef]()
+ })
+ }
+
+ private def fetchHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec:OntologyEngineContext): Future[Any] = {
+ val req = new Request(request)
+ req.put(HierarchyConstants.IDENTIFIER, rootNode.getIdentifier)
+ oec.graphService.readExternalProps(req, List(HierarchyConstants.HIERARCHY)).map(response => {
+ if (ResponseHandler.checkError(response) && ResponseHandler.isResponseNotFoundError(response)) {
+ if (CollectionUtils.containsAny(HierarchyConstants.HIERARCHY_LIVE_STATUS, rootNode.getMetadata.get("status").asInstanceOf[String]))
+ throw new ServerException(HierarchyErrorCodes.ERR_HIERARCHY_NOT_FOUND, "No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier)
+ else {
+ if (rootNode.getMetadata.containsKey("pkgVersion"))
+ req.put(HierarchyConstants.IDENTIFIER, rootNode.getIdentifier.replace(HierarchyConstants.IMAGE_SUFFIX, ""))
+ else {
+ req.put(HierarchyConstants.IDENTIFIER, if (!rootNode.getIdentifier.endsWith(HierarchyConstants.IMAGE_SUFFIX)) rootNode.getIdentifier + HierarchyConstants.IMAGE_SUFFIX else rootNode.getIdentifier)
+ }
+ oec.graphService.readExternalProps(req, List(HierarchyConstants.HIERARCHY)).map(resp => {
+ resp.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String]
+ }) recover { case e: ResourceNotFoundException => TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier) }
+ }
+ } else Future(response.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String])
+ }).flatMap(f => f)
+ }
+
+ private def addChildNodesInNodeList(childrenMaps: java.util.List[java.util.Map[String, AnyRef]], request: Request, nodes: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[scala.collection.immutable.List[Node]] = {
+ if (CollectionUtils.isNotEmpty(childrenMaps)) {
+ val futures = childrenMaps.map(child => {
+ addNodeToList(child, request, nodes).map(modifiedList => {
+ if (!StringUtils.equalsIgnoreCase(HierarchyConstants.DEFAULT, child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+ addChildNodesInNodeList(child.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]], request, modifiedList)
+ } else
+ Future(modifiedList)
+ }).flatMap(f => f)
+ }).toList
+ Future.sequence(futures).map(f => f.flatten.distinct)
+ } else {
+ Future(nodes)
+ }
+ }
+
+ private def addNodeToList(child: java.util.Map[String, AnyRef], request: Request, nodes: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[scala.collection.immutable.List[Node]] = {
+ if (StringUtils.isNotEmpty(child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String]))
+ if (StringUtils.equalsIgnoreCase(HierarchyConstants.DEFAULT, child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+ getQuestionNode(child.getOrDefault(HierarchyConstants.IDENTIFIER, "").asInstanceOf[String], HierarchyConstants.TAXONOMY_ID).map(node => {
+ node.getMetadata.put(HierarchyConstants.DEPTH, child.get(HierarchyConstants.DEPTH))
+ node.getMetadata.put(HierarchyConstants.PARENT, child.get(HierarchyConstants.PARENT))
+ node.getMetadata.put(HierarchyConstants.INDEX, child.get(HierarchyConstants.INDEX))
+ node.setObjectType(HierarchyConstants.QUESTION_OBJECT_TYPE)
+ node.getMetadata.put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.QUESTION_OBJECT_TYPE)
+ val updatedNodes = node :: nodes
+ updatedNodes
+ }) recoverWith { case e: CompletionException => throw e.getCause }
+ } else {
+ val childData: java.util.Map[String, AnyRef] = new java.util.HashMap[String, AnyRef]
+ childData.putAll(child)
+ childData.remove(HierarchyConstants.CHILDREN)
+ childData.put(HierarchyConstants.STATUS, "Draft")
+ val rootNode = getTempNode(nodes, request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String])
+ childData.put(HierarchyConstants.CHANNEL, rootNode.getMetadata.get(HierarchyConstants.CHANNEL))
+ val node = NodeUtil.deserialize(childData, request.getContext.get(HierarchyConstants.SCHEMA_NAME).asInstanceOf[String], DefinitionNode.getRelationsMap(request))
+ node.setObjectType(node.getMetadata.getOrDefault("objectType", "").asInstanceOf[String])
+ val updatedNodes = node :: nodes
+ Future(updatedNodes)
+ }
+ else {
+ Future(nodes)
+ }
+ }
+
+
+ private def updateNodesModifiedInNodeList(nodeList: List[Node], nodesModified: java.util.HashMap[String, AnyRef], request: Request, idMap: mutable.Map[String, String])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ updateRootNode(request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String], nodeList, nodesModified)
+ val futures = nodesModified.filter(nodeModified => !StringUtils.startsWith(request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String], nodeModified._1))
+ .map(nodeModified => {
+ val objectType = nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.OBJECT_TYPE, "").asInstanceOf[String]
+ if(StringUtils.isBlank(objectType))
+ throw new ClientException("ERR_UPDATE_QS_HIERARCHY", s"Object Type is mandatory for creation of node with id: ${nodeModified._1}")
+ val metadata = nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ if(!StringUtils.equalsIgnoreCase(metadata.getOrDefault(HierarchyConstants.VISIBILITY, "Parent").asInstanceOf[String], "Parent"))
+ throw new ClientException("ERR_UPDATE_QS_HIERARCHY", s"Visibility can be only of type Parent for identifier: ${nodeModified._1}")
+ metadata.remove(HierarchyConstants.DIALCODES)
+ metadata.put(HierarchyConstants.STATUS, "Draft")
+ metadata.put(HierarchyConstants.LAST_UPDATED_ON, DateUtils.formatCurrentDate)
+ metadata.put(HierarchyConstants.OBJECT_TYPE, objectType)
+ if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].containsKey(HierarchyConstants.IS_NEW)
+ && nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.IS_NEW).asInstanceOf[Boolean]) {
+ if (!nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean])
+ metadata.put(HierarchyConstants.VISIBILITY, HierarchyConstants.PARENT)
+ if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].contains(HierarchyConstants.SET_DEFAULT_VALUE))
+ createNewNode(nodeModified._1, idMap, metadata, nodeList, request, nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.SET_DEFAULT_VALUE).asInstanceOf[Boolean])
+ else
+ createNewNode(nodeModified._1, idMap, metadata, nodeList, request)
+ } else {
+ updateTempNode(request, nodeModified._1, nodeList, idMap, metadata)
+ }
+ })
+ if (CollectionUtils.isNotEmpty(futures))
+ Future.sequence(futures.toList).map(f => f.flatten)
+ else Future(nodeList)
+ }
+
+ private def updateRootNode(rootId: String, nodeList: List[Node], nodesModified: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext): Unit = {
+ if (nodesModified.containsKey(rootId)) {
+ val metadata = nodesModified.getOrDefault(rootId, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ updateNodeList(nodeList, rootId, metadata)
+ nodesModified.remove(rootId)
+ }
+ }
+
+ private def createNewNode(nodeId: String, idMap: mutable.Map[String, String], metadata: java.util.HashMap[String, AnyRef], nodeList: List[Node], request: Request, setDefaultValue: Boolean = true)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val objectType = metadata.getOrDefault("objectType", "").asInstanceOf[String]
+ metadata.remove("objectType")
+ val identifier: String = Identifier.getIdentifier(HierarchyConstants.TAXONOMY_ID, Identifier.getUniqueIdFromTimestamp)
+ idMap += (nodeId -> identifier)
+ metadata.put(HierarchyConstants.IDENTIFIER, identifier)
+ metadata.put(HierarchyConstants.CODE, nodeId)
+ metadata.put(HierarchyConstants.VERSION_KEY, System.currentTimeMillis + "")
+ metadata.put(HierarchyConstants.CREATED_ON, DateUtils.formatCurrentDate)
+ metadata.put(HierarchyConstants.LAST_STATUS_CHANGED_ON, DateUtils.formatCurrentDate)
+ val rootNode = getTempNode(nodeList, request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String])
+ metadata.put(HierarchyConstants.CHANNEL, rootNode.getMetadata.get(HierarchyConstants.CHANNEL))
+ val createRequest: Request = new Request(request)
+ createRequest.setRequest(metadata)
+ if (neo4jCreateTypes.contains(objectType)) {
+ createRequest.getContext.put(HierarchyConstants.SCHEMA_NAME, "question")
+ DataNode.create(createRequest).map(node => {
+ node.setGraphId(HierarchyConstants.TAXONOMY_ID)
+ node.setNodeType(HierarchyConstants.DATA_NODE)
+ node.setObjectType(objectType)
+ val updatedList = node :: nodeList
+ updatedList.distinct
+ })
+
+ } else
+ DefinitionNode.validate(createRequest, setDefaultValue).map(node => {
+ node.setGraphId(HierarchyConstants.TAXONOMY_ID)
+ node.setNodeType(HierarchyConstants.DATA_NODE)
+ node.setObjectType(objectType)
+ val updatedList = node :: nodeList
+ updatedList.distinct
+ })
+ }
+
+ private def updateTempNode(request:Request, nodeId: String, nodeList: List[Node], idMap: mutable.Map[String, String], metadata: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val tempNode: Node = getTempNode(nodeList, nodeId)
+ if(null == tempNode)
+ throw new ResourceNotFoundException("ERR_UPDATE_QS_HIERARCHY", s"No node found with id: $nodeId")
+ else {
+ val objectType = metadata.getOrDefault("objectType", "").asInstanceOf[String]
+ metadata.put(HierarchyConstants.IDENTIFIER, tempNode.getIdentifier)
+ val createRequest: Request = new Request(request)
+ createRequest.setRequest(metadata)
+ if (neo4jCreateTypes.contains(objectType)) {
+ createRequest.getContext.put(HierarchyConstants.IDENTIFIER, tempNode.getIdentifier)
+ createRequest.getContext.put(HierarchyConstants.SCHEMA_NAME, "question")
+ createRequest.getContext.put(HierarchyConstants.OBJECT_TYPE, objectType)
+ DataNode.update(createRequest).map(node => {
+ idMap += (nodeId -> node.getIdentifier)
+ updateNodeList(nodeList, node.getIdentifier, node.getMetadata)
+ nodeList
+ })
+ } else {
+ if (null != tempNode && StringUtils.isNotBlank(tempNode.getIdentifier)) {
+ metadata.put(HierarchyConstants.IDENTIFIER, tempNode.getIdentifier)
+ idMap += (nodeId -> tempNode.getIdentifier)
+ updateNodeList(nodeList, tempNode.getIdentifier, metadata)
+ Future(nodeList)
+ } else throw new ResourceNotFoundException(HierarchyErrorCodes.ERR_CONTENT_NOT_FOUND, "Content not found with identifier: " + nodeId)
+ }
+ }
+ }
+
+ private def validateNodes(nodeList: java.util.List[Node], rootId: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val nodesToValidate = nodeList.filter(node => (StringUtils.equals(HierarchyConstants.PARENT, node.getMetadata.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])
+ && !StringUtils.equalsIgnoreCase("Question", node.getObjectType))
+ || StringUtils.equalsAnyIgnoreCase(rootId, node.getIdentifier)).toList
+ DefinitionNode.updateJsonPropsInNodes(nodeList.toList, HierarchyConstants.TAXONOMY_ID, HierarchyConstants.QUESTIONSET_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION)
+ DefinitionNode.validateContentNodes(nodesToValidate, HierarchyConstants.TAXONOMY_ID, HierarchyConstants.QUESTIONSET_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION)
+ }
+
+ def constructHierarchy(list: List[java.util.Map[String, AnyRef]]): java.util.Map[String, AnyRef] = {
+ val hierarchy: java.util.Map[String, AnyRef] = list.filter(root => root.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == 0).head
+ if (MapUtils.isNotEmpty(hierarchy)) {
+ val maxDepth = list.map(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue()).max
+ for (i <- 0 to maxDepth) {
+ val depth = i
+ val currentLevelNodes: Map[String, List[java.util.Map[String, Object]]] = list.filter(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == depth).groupBy(_.get("identifier").asInstanceOf[String].replaceAll(".img", ""))
+ val nextLevel: List[java.util.Map[String, AnyRef]] = list.filter(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == (depth + 1))
+ if (CollectionUtils.isNotEmpty(nextLevel) && MapUtils.isNotEmpty(currentLevelNodes)) {
+ nextLevel.foreach(e => {
+ val parentId = e.get("parent").asInstanceOf[String]
+ currentLevelNodes.getOrDefault(parentId, List[java.util.Map[String, AnyRef]]()).foreach(parent => {
+ val children = parent.getOrDefault(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.Map[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ children.add(e)
+ parent.put(HierarchyConstants.CHILDREN, sortByIndex(children))
+ })
+ })
+ }
+ }
+ }
+ hierarchy
+ }
+
+ @throws[Exception]
+ private def getChildrenHierarchy(nodeList: List[Node], rootId: String, hierarchyData: java.util.HashMap[String, AnyRef], idMap: mutable.Map[String, String], existingHierarchy: java.util.Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[java.util.List[java.util.Map[String, AnyRef]]] = {
+ val childrenIdentifiersMap: Map[String, Map[String, Int]] = getChildrenIdentifiersMap(hierarchyData, idMap, existingHierarchy)
+ getPreparedHierarchyData(nodeList, rootId, childrenIdentifiersMap).map(nodeMaps => {
+ TelemetryManager.info("prepared hierarchy list without filtering: " + nodeMaps.size())
+ val filteredNodeMaps = nodeMaps.filter(nodeMap => null != nodeMap.get(HierarchyConstants.DEPTH)).toList
+ TelemetryManager.info("prepared hierarchy list with filtering: " + filteredNodeMaps.size())
+ val hierarchyMap = constructHierarchy(filteredNodeMaps)
+ if (MapUtils.isNotEmpty(hierarchyMap)) {
+ hierarchyMap.getOrDefault(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.Map[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ .filter(child => MapUtils.isNotEmpty(child))
+ }
+ else
+ new java.util.ArrayList[java.util.Map[String, AnyRef]]()
+
+ })
+ }
+
+ private def getChildrenIdentifiersMap(hierarchyData: java.util.Map[String, AnyRef], idMap: mutable.Map[String, String], existingHierarchy: java.util.Map[String, AnyRef]): Map[String, Map[String, Int]] = {
+ if (MapUtils.isNotEmpty(hierarchyData)) {
+ hierarchyData.map(entry => idMap.getOrDefault(entry._1, entry._1) -> entry._2.asInstanceOf[java.util.HashMap[String, AnyRef]]
+ .get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[String]]
+ .map(id => idMap.getOrDefault(id, id)).zipWithIndex.toMap).toMap
+ } else {
+ val tempChildMap: java.util.Map[String, Map[String, Int]] = new java.util.HashMap[String, Map[String, Int]]()
+ val tempResourceMap: java.util.Map[String, Map[String, Int]] = new java.util.HashMap[String, Map[String, Int]]()
+ getChildrenIdMapFromExistingHierarchy(existingHierarchy, tempChildMap, tempResourceMap)
+ tempChildMap.putAll(tempResourceMap)
+ tempChildMap.toMap
+ }
+ }
+
+ private def getChildrenIdMapFromExistingHierarchy(existingHierarchy: java.util.Map[String, AnyRef], tempChildMap: java.util.Map[String, Map[String, Int]], tempResourceMap: java.util.Map[String, Map[String, Int]]): Unit = {
+ if (existingHierarchy.containsKey(HierarchyConstants.CHILDREN) && CollectionUtils.isNotEmpty(existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]])) {
+ tempChildMap.put(existingHierarchy.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String], existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]]
+ .map(child => child.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String] -> child.get(HierarchyConstants.INDEX).asInstanceOf[Int]).toMap)
+ existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]]
+ .foreach(child => getChildrenIdMapFromExistingHierarchy(child, tempChildMap, tempResourceMap))
+ } else
+ tempResourceMap.put(existingHierarchy.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String], Map[String, Int]())
+ }
+
+ @throws[Exception]
+ private def getPreparedHierarchyData(nodeList: List[Node], rootId: String, childrenIdentifiersMap: Map[String, Map[String, Int]])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[java.util.List[java.util.Map[String, AnyRef]]] = {
+ if (MapUtils.isNotEmpty(childrenIdentifiersMap)) {
+ val updatedNodeList = getTempNode(nodeList, rootId) :: List()
+ updateHierarchyRelatedData(childrenIdentifiersMap.getOrElse(rootId, Map[String, Int]()), 1,
+ rootId, nodeList, childrenIdentifiersMap, updatedNodeList).map(finalEnrichedNodeList => {
+ TelemetryManager.info("Final enriched list size: " + finalEnrichedNodeList.size)
+ val childNodeIds = finalEnrichedNodeList.map(node => node.getIdentifier).filterNot(id => rootId.equalsIgnoreCase(id)).distinct
+ TelemetryManager.info("Final enriched ids (childNodes): " + childNodeIds + " :: size: " + childNodeIds.size)
+ updateNodeList(nodeList, rootId, new java.util.HashMap[String, AnyRef]() {
+ put(HierarchyConstants.DEPTH, 0.asInstanceOf[AnyRef])
+ put(HierarchyConstants.CHILD_NODES, new java.util.ArrayList[String](childNodeIds))
+ })
+ validateNodes(finalEnrichedNodeList, rootId).map(result => HierarchyManager.convertNodeToMap(finalEnrichedNodeList))
+ }).flatMap(f => f)
+ } else {
+ updateNodeList(nodeList, rootId, new java.util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.DEPTH, 0.asInstanceOf[AnyRef])
+ }
+ })
+ validateNodes(nodeList, rootId).map(result => HierarchyManager.convertNodeToMap(nodeList))
+ }
+ }
+
+ @throws[Exception]
+ private def updateHierarchyRelatedData(childrenIds: Map[String, Int], depth: Int, parent: String, nodeList: List[Node], hierarchyStructure: Map[String, Map[String, Int]], enrichedNodeList: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[List[Node]] = {
+ val futures = childrenIds.map(child => {
+ val id = child._1
+ val index = child._2 + 1
+ val tempNode = getTempNode(nodeList, id)
+ if (null != tempNode && StringUtils.equalsIgnoreCase(HierarchyConstants.PARENT, tempNode.getMetadata.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+ populateHierarchyRelatedData(tempNode, depth, index, parent)
+ val nxtEnrichedNodeList = tempNode :: enrichedNodeList
+ if (MapUtils.isNotEmpty(hierarchyStructure.getOrDefault(child._1, Map[String, Int]())))
+ updateHierarchyRelatedData(hierarchyStructure.getOrDefault(child._1, Map[String, Int]()),
+ tempNode.getMetadata.get(HierarchyConstants.DEPTH).asInstanceOf[Int] + 1, id, nodeList, hierarchyStructure, nxtEnrichedNodeList)
+ else
+ Future(nxtEnrichedNodeList)
+ } else {
+ getQuestionNode(id, HierarchyConstants.TAXONOMY_ID).map(node => {
+ populateHierarchyRelatedData(node, depth, index, parent)
+ //node.getMetadata.put(HierarchyConstants.VISIBILITY, HierarchyConstants.DEFAULT)
+ node.setObjectType(HierarchyConstants.QUESTION_OBJECT_TYPE)
+ node.getMetadata.put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.QUESTION_OBJECT_TYPE)
+ val nxtEnrichedNodeList = node :: enrichedNodeList
+ if (MapUtils.isNotEmpty(hierarchyStructure.getOrDefault(id, Map[String, Int]()))) {
+ updateHierarchyRelatedData(hierarchyStructure.getOrDefault(id, Map[String, Int]()), node.getMetadata.get(HierarchyConstants.DEPTH).asInstanceOf[Int] + 1, id, nodeList, hierarchyStructure, nxtEnrichedNodeList)
+ } else
+ Future(nxtEnrichedNodeList)
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+ })
+ if (CollectionUtils.isNotEmpty(futures)) {
+ val listOfFutures = Future.sequence(futures.toList)
+ listOfFutures.map(f => f.flatten.distinct)
+ } else
+ Future(enrichedNodeList)
+ }
+
+ private def populateHierarchyRelatedData(tempNode: Node, depth: Int, index: Int, parent: String) = {
+ tempNode.getMetadata.put(HierarchyConstants.DEPTH, depth.asInstanceOf[AnyRef])
+ tempNode.getMetadata.put(HierarchyConstants.PARENT, parent.replaceAll(".img", ""))
+ tempNode.getMetadata.put(HierarchyConstants.INDEX, index.asInstanceOf[AnyRef])
+ }
+
+ /**
+ * This method is to check if all the children of the parent entity are present in the populated map
+ *
+ * @param children
+ * @param populatedChildMap
+ * @return
+ */
+ def isFullyPopulated(children: List[String], populatedChildMap: mutable.Map[_, _]): Boolean = {
+ children.forall(child => populatedChildMap.containsKey(child))
+ }
+
+ def updateHierarchyData(rootId: String, children: java.util.List[java.util.Map[String, AnyRef]], nodeList: List[Node], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val node = getTempNode(nodeList, rootId)
+ val updatedHierarchy = new java.util.HashMap[String, AnyRef]()
+ updatedHierarchy.put(HierarchyConstants.IDENTIFIER, rootId)
+ updatedHierarchy.put(HierarchyConstants.CHILDREN, children)
+ val req = new Request(request)
+ req.getContext.put(HierarchyConstants.IDENTIFIER, rootId)
+ val metadata = cleanUpRootData(node)
+ req.getRequest.putAll(metadata)
+ req.put(HierarchyConstants.HIERARCHY, ScalaJsonUtils.serialize(updatedHierarchy))
+ req.put(HierarchyConstants.IDENTIFIER, rootId)
+ req.put(HierarchyConstants.CHILDREN, new java.util.ArrayList())
+ DataNode.update(req)
+ }
+
+ private def cleanUpRootData(node: Node)(implicit oec: OntologyEngineContext, ec: ExecutionContext): java.util.Map[String, AnyRef] = {
+ DefinitionNode.getRestrictedProperties(HierarchyConstants.TAXONOMY_ID, HierarchyConstants.SCHEMA_VERSION, HierarchyConstants.OPERATION_UPDATE_HIERARCHY, HierarchyConstants.QUESTIONSET_SCHEMA_NAME)
+ .foreach(key => node.getMetadata.remove(key))
+ node.getMetadata.remove(HierarchyConstants.STATUS)
+ node.getMetadata.remove(HierarchyConstants.LAST_UPDATED_ON)
+ node.getMetadata.remove(HierarchyConstants.LAST_STATUS_CHANGED_ON)
+ node.getMetadata
+ }
+
+ /**
+ * Get the Node with ID provided from List else return Null.
+ *
+ * @param nodeList
+ * @param id
+ * @return
+ */
+ private def getTempNode(nodeList: List[Node], id: String) = {
+ nodeList.find(node => StringUtils.startsWith(node.getIdentifier, id)).orNull
+ }
+
+ private def updateNodeList(nodeList: List[Node], id: String, metadata: java.util.Map[String, AnyRef]): Unit = {
+ nodeList.foreach(node => {
+ if(node.getIdentifier.startsWith(id)){
+ node.getMetadata.putAll(metadata)
+ }
+ })
+ }
+
+ def getQuestionNode(identifier: String, graphId: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val request: Request = new Request()
+ request.setContext(new java.util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.GRAPH_ID, graphId)
+ put(HierarchyConstants.VERSION, HierarchyConstants.SCHEMA_VERSION)
+ put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.QUESTION_OBJECT_TYPE)
+ put(HierarchyConstants.SCHEMA_NAME, HierarchyConstants.QUESTION_SCHEMA_NAME)
+ }
+ })
+ request.setObjectType(HierarchyConstants.QUESTION_OBJECT_TYPE)
+ request.put(HierarchyConstants.IDENTIFIER, identifier)
+ request.put(HierarchyConstants.MODE, HierarchyConstants.READ_MODE)
+ request.put(HierarchyConstants.FIELDS, new java.util.ArrayList[String]())
+ DataNode.read(request)
+ }
+
+
+ def sortByIndex(childrenMaps: java.util.List[java.util.Map[String, AnyRef]]): java.util.List[java.util.Map[String, AnyRef]] = {
+ bufferAsJavaList(childrenMaps.sortBy(_.get("index").asInstanceOf[Int]))
+ }
+
+
+ def deleteHierarchy(request: Request)(implicit ec: ExecutionContext): Future[Response] = {
+ val req = new Request(request)
+ val rootId = request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String]
+ req.put(HierarchyConstants.IDENTIFIERS, if (rootId.contains(HierarchyConstants.IMAGE_SUFFIX)) List(rootId) else List(rootId + HierarchyConstants.IMAGE_SUFFIX))
+ ExternalPropsManager.deleteProps(req)
+ }
+
+}
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala
new file mode 100644
index 000000000..badef1915
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala
@@ -0,0 +1,53 @@
+package org.sunbird.utils
+
+object HierarchyConstants {
+ val DATA: String = "data"
+ val DATA_NODE: String = "DATA_NODE"
+ val NODES_MODIFIED: String = "nodesModified"
+ val HIERARCHY: String = "hierarchy"
+ val ROOT: String = "root"
+ val SET_DEFAULT_VALUE: String = "setDefaultValue"
+ val VERSION: String = "version"
+ val IDENTIFIER: String = "identifier"
+ val DEPTH: String = "depth"
+ val PARENT: String = "Parent"
+ val INDEX: String = "index"
+ val CHILDREN: String = "children"
+ val VISIBILITY: String = "visibility"
+ val TAXONOMY_ID: String = "domain"
+ val METADATA: String = "metadata"
+ val IS_NEW: String = "isNew"
+ val DIALCODES: String = "dialcodes"
+ val QUESTION_OBJECT_TYPE: String = "Question"
+ val OBJECT_TYPE = "objectType"
+ val STATUS: String = "status"
+ val LAST_UPDATED_ON: String = "lastUpdatedOn"
+ val CODE: String = "code"
+ val VERSION_KEY: String = "versionKey"
+ val CREATED_ON: String = "createdOn"
+ val LAST_STATUS_CHANGED_ON: String = "lastStatusChangedOn"
+ val CHILD_NODES: String = "childNodes"
+ val QUESTION_SCHEMA_NAME: String = "question"
+ val QUESTIONSET_SCHEMA_NAME: String = "questionset"
+ val SCHEMA_NAME: String = "schemaName"
+ val SCHEMA_VERSION: String = "1.0"
+ val CONTENT_ID: String = "identifier"
+ val IDENTIFIERS: String = "identifiers"
+ val DEFAULT: String = "Default"
+ val CHANNEL: String = "channel"
+ val ROOT_ID: String = "rootId"
+ val HIERARCHY_LIVE_STATUS: List[String] = List("Live", "Unlisted", "Flagged")
+ val IMAGE_SUFFIX: String = ".img"
+ val GRAPH_ID: String = "graph_id"
+ val MODE: String = "mode"
+ val EDIT_MODE: String = "edit"
+ val READ_MODE: String = "read"
+ val CONCEPTS: String = "concepts"
+ val FIELDS: String = "fields"
+ val MIME_TYPE: String = "mimeType"
+ val RETIRED_STATUS: String = "Retired"
+ val AUDIENCE: String = "audience"
+ val QUESTIONSET_MIME_TYPE: String = "application/vnd.sunbird.questionset"
+ val QUESTIONSET_OBJECT_TYPE: String = "QuestionSet"
+ val OPERATION_UPDATE_HIERARCHY: String = "updateHierarchy"
+}
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala
new file mode 100644
index 000000000..7b7e8b9ec
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala
@@ -0,0 +1,11 @@
+package org.sunbird.utils
+
+object HierarchyErrorCodes {
+ val ERR_INVALID_ROOT_ID: String = "ERR_INVALID_ROOT_ID"
+ val ERR_CONTENT_NOT_FOUND: String = "ERR_CONTENT_NOT_FOUND"
+ val ERR_HIERARCHY_NOT_FOUND: String = "ERR_HIERARCHY_NOT_FOUND"
+ val ERR_HIERARCHY_UPDATE_DENIED: String = "ERR_HIERARCHY_UPDATE_DENIED"
+ val ERR_ADD_HIERARCHY_DENIED: String = "ERR_ADD_HIERARCHY_DENIED"
+ val ERR_REMOVE_HIERARCHY_DENIED: String = "ERR_REMOVE_HIERARCHY_DENIED"
+
+}
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/JavaJsonUtils.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/JavaJsonUtils.scala
new file mode 100644
index 000000000..8f1275ef3
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/JavaJsonUtils.scala
@@ -0,0 +1,36 @@
+package org.sunbird.utils
+
+import java.lang.reflect.{ParameterizedType, Type}
+
+import com.fasterxml.jackson.core.`type`.TypeReference
+import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
+
+object JavaJsonUtils {
+
+ @transient val mapper = new ObjectMapper()
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+
+ @throws(classOf[Exception])
+ def serialize(obj: AnyRef): String = {
+ mapper.writeValueAsString(obj)
+ }
+
+ @throws(classOf[Exception])
+ def deserialize[T: Manifest](value: String): T = mapper.readValue(value, typeReference[T])
+
+ private[this] def typeReference[T: Manifest] = new TypeReference[T] {
+ override def getType = typeFromManifest(manifest[T])
+ }
+
+
+ private[this] def typeFromManifest(m: Manifest[_]): Type = {
+ if (m.typeArguments.isEmpty) { m.runtimeClass }
+ // $COVERAGE-OFF$Disabling scoverage as this code is impossible to test
+ else new ParameterizedType {
+ def getRawType = m.runtimeClass
+ def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
+ def getOwnerType = null
+ }
+ // $COVERAGE-ON$
+ }
+}
diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala
new file mode 100644
index 000000000..e69de29bb
diff --git a/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf b/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf
new file mode 100644
index 000000000..046ae1a25
--- /dev/null
+++ b/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf
@@ -0,0 +1,536 @@
+# This is the main configuration file for the application.
+# https://www.playframework.com/documentation/latest/ConfigFile
+# ~~~~~
+# Play uses HOCON as its configuration file format. HOCON has a number
+# of advantages over other config formats, but there are two things that
+# can be used when modifying settings.
+#
+# You can include other configuration files in this main application.conf file:
+#include "extra-config.conf"
+#
+# You can declare variables and substitute for them:
+#mykey = ${some.value}
+#
+# And if an environment variable exists when there is no other substitution, then
+# HOCON will fall back to substituting environment variable:
+#mykey = ${JAVA_HOME}
+
+## Akka
+# https://www.playframework.com/documentation/latest/ScalaAkka#Configuration
+# https://www.playframework.com/documentation/latest/JavaAkka#Configuration
+# ~~~~~
+# Play uses Akka internally and exposes Akka Streams and actors in Websockets and
+# other streaming HTTP responses.
+akka {
+ # "akka.log-config-on-start" is extraordinarly useful because it log the complete
+ # configuration at INFO level, including defaults and overrides, so it s worth
+ # putting at the very top.
+ #
+ # Put the following in your conf/logback.xml file:
+ #
+ #
+ #
+ # And then uncomment this line to debug the configuration.
+ #
+ #log-config-on-start = true
+}
+
+## Secret key
+# http://www.playframework.com/documentation/latest/ApplicationSecret
+# ~~~~~
+# The secret key is used to sign Play's session cookie.
+# This must be changed for production, but we don't recommend you change it in this file.
+play.http.secret.key = a-long-secret-to-calm-the-rage-of-the-entropy-gods
+
+## Modules
+# https://www.playframework.com/documentation/latest/Modules
+# ~~~~~
+# Control which modules are loaded when Play starts. Note that modules are
+# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
+# Please see https://www.playframework.com/documentation/latest/GlobalSettings
+# for more information.
+#
+# You can also extend Play functionality by using one of the publically available
+# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
+play.modules {
+ # By default, Play will load any class called Module that is defined
+ # in the root package (the "app" directory), or you can define them
+ # explicitly below.
+ # If there are any built-in modules that you want to enable, you can list them here.
+ #enabled += my.application.Module
+
+ # If there are any built-in modules that you want to disable, you can list them here.
+ #disabled += ""
+}
+
+## IDE
+# https://www.playframework.com/documentation/latest/IDE
+# ~~~~~
+# Depending on your IDE, you can add a hyperlink for errors that will jump you
+# directly to the code location in the IDE in dev mode. The following line makes
+# use of the IntelliJ IDEA REST interface:
+#play.editor="http://localhost:63342/api/file/?file=%s&line=%s"
+
+## Internationalisation
+# https://www.playframework.com/documentation/latest/JavaI18N
+# https://www.playframework.com/documentation/latest/ScalaI18N
+# ~~~~~
+# Play comes with its own i18n settings, which allow the user's preferred language
+# to map through to internal messages, or allow the language to be stored in a cookie.
+play.i18n {
+ # The application languages
+ langs = [ "en" ]
+
+ # Whether the language cookie should be secure or not
+ #langCookieSecure = true
+
+ # Whether the HTTP only attribute of the cookie should be set to true
+ #langCookieHttpOnly = true
+}
+
+## Play HTTP settings
+# ~~~~~
+play.http {
+ ## Router
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # Define the Router object to use for this application.
+ # This router will be looked up first when the application is starting up,
+ # so make sure this is the entry point.
+ # Furthermore, it's assumed your route file is named properly.
+ # So for an application router like `my.application.Router`,
+ # you may need to define a router file `conf/my.application.routes`.
+ # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
+ #router = my.application.Router
+
+ ## Action Creator
+ # https://www.playframework.com/documentation/latest/JavaActionCreator
+ # ~~~~~
+ #actionCreator = null
+
+ ## ErrorHandler
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # If null, will attempt to load a class called ErrorHandler in the root package,
+ #errorHandler = null
+
+ ## Session & Flash
+ # https://www.playframework.com/documentation/latest/JavaSessionFlash
+ # https://www.playframework.com/documentation/latest/ScalaSessionFlash
+ # ~~~~~
+ session {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+
+ # Sets the max-age field of the cookie to 5 minutes.
+ # NOTE: this only sets when the browser will discard the cookie. Play will consider any
+ # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
+ # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
+ #maxAge = 300
+
+ # Sets the domain on the session cookie.
+ #domain = "example.com"
+ }
+
+ flash {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+ }
+}
+
+## Netty Provider
+# https://www.playframework.com/documentation/latest/SettingsNetty
+# ~~~~~
+play.server.netty {
+ # Whether the Netty wire should be logged
+ log.wire = true
+
+ # If you run Play on Linux, you can use Netty's native socket transport
+ # for higher performance with less garbage.
+ transport = "native"
+}
+
+## WS (HTTP Client)
+# https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS
+# ~~~~~
+# The HTTP client primarily used for REST APIs. The default client can be
+# configured directly, but you can also create different client instances
+# with customized settings. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += ws // or javaWs if using java
+#
+play.ws {
+ # Sets HTTP requests not to follow 302 requests
+ #followRedirects = false
+
+ # Sets the maximum number of open HTTP connections for the client.
+ #ahc.maxConnectionsTotal = 50
+
+ ## WS SSL
+ # https://www.playframework.com/documentation/latest/WsSSL
+ # ~~~~~
+ ssl {
+ # Configuring HTTPS with Play WS does not require programming. You can
+ # set up both trustManager and keyManager for mutual authentication, and
+ # turn on JSSE debugging in development with a reload.
+ #debug.handshake = true
+ #trustManager = {
+ # stores = [
+ # { type = "JKS", path = "exampletrust.jks" }
+ # ]
+ #}
+ }
+}
+
+## Cache
+# https://www.playframework.com/documentation/latest/JavaCache
+# https://www.playframework.com/documentation/latest/ScalaCache
+# ~~~~~
+# Play comes with an integrated cache API that can reduce the operational
+# overhead of repeated requests. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += cache
+#
+play.cache {
+ # If you want to bind several caches, you can bind the individually
+ #bindCaches = ["db-cache", "user-cache", "session-cache"]
+}
+
+## Filter Configuration
+# https://www.playframework.com/documentation/latest/Filters
+# ~~~~~
+# There are a number of built-in filters that can be enabled and configured
+# to give Play greater security.
+#
+play.filters {
+
+ # Enabled filters are run automatically against Play.
+ # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
+ enabled = []
+
+ # Disabled filters remove elements from the enabled list.
+ # disabled += filters.CSRFFilter
+
+
+ ## CORS filter configuration
+ # https://www.playframework.com/documentation/latest/CorsFilter
+ # ~~~~~
+ # CORS is a protocol that allows web applications to make requests from the browser
+ # across different domains.
+ # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
+ # dependencies on CORS settings.
+ cors {
+ # Filter paths by a whitelist of path prefixes
+ #pathPrefixes = ["/some/path", ...]
+
+ # The allowed origins. If null, all origins are allowed.
+ #allowedOrigins = ["http://www.example.com"]
+
+ # The allowed HTTP methods. If null, all methods are allowed
+ #allowedHttpMethods = ["GET", "POST"]
+ }
+
+ ## Security headers filter configuration
+ # https://www.playframework.com/documentation/latest/SecurityHeaders
+ # ~~~~~
+ # Defines security headers that prevent XSS attacks.
+ # If enabled, then all options are set to the below configuration by default:
+ headers {
+ # The X-Frame-Options header. If null, the header is not set.
+ #frameOptions = "DENY"
+
+ # The X-XSS-Protection header. If null, the header is not set.
+ #xssProtection = "1; mode=block"
+
+ # The X-Content-Type-Options header. If null, the header is not set.
+ #contentTypeOptions = "nosniff"
+
+ # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
+ #permittedCrossDomainPolicies = "master-only"
+
+ # The Content-Security-Policy header. If null, the header is not set.
+ #contentSecurityPolicy = "default-src 'self'"
+ }
+
+ ## Allowed hosts filter configuration
+ # https://www.playframework.com/documentation/latest/AllowedHostsFilter
+ # ~~~~~
+ # Play provides a filter that lets you configure which hosts can access your application.
+ # This is useful to prevent cache poisoning attacks.
+ hosts {
+ # Allow requests to example.com, its subdomains, and localhost:9000.
+ #allowed = [".example.com", "localhost:9000"]
+ }
+}
+
+# Learning-Service Configuration
+content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit"]
+
+# Cassandra Configuration
+content.keyspace.name=content_store
+content.keyspace.table=content_data
+#TODO: Add Configuration for assessment. e.g: question_data
+orchestrator.keyspace.name=script_store
+orchestrator.keyspace.table=script_data
+cassandra.lp.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+cassandra.lpa.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+
+# Redis Configuration
+redis.host=localhost
+redis.port=6379
+redis.maxConnections=128
+
+#Condition to enable publish locally
+content.publish_task.enabled=true
+
+#directory location where store unzip file
+dist.directory=/data/tmp/dist/
+output.zipfile=/data/tmp/story.zip
+source.folder=/data/tmp/temp2/
+save.directory=/data/tmp/temp/
+
+# Content 2 vec analytics URL
+CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec"
+
+# FOR CONTENT WORKFLOW PIPELINE (CWP)
+
+#--Content Workflow Pipeline Mode
+OPERATION_MODE=TEST
+
+#--Maximum Content Package File Size Limit in Bytes (50 MB)
+MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800
+
+#--Maximum Asset File Size Limit in Bytes (20 MB)
+MAX_ASSET_FILE_SIZE_LIMIT=20971520
+
+#--No of Retry While File Download Fails
+RETRY_ASSET_DOWNLOAD_COUNT=1
+
+#Google-vision-API
+google.vision.tagging.enabled = false
+
+#Orchestrator env properties
+env="https://dev.ekstep.in/api/learning"
+
+#Current environment
+cloud_storage.env=dev
+
+
+#Folder configuration
+cloud_storage.content.folder=content
+cloud_storage.asset.folder=assets
+cloud_storage.artefact.folder=artifact
+cloud_storage.bundle.folder=bundle
+cloud_storage.media.folder=media
+cloud_storage.ecar.folder=ecar_files
+
+# Media download configuration
+content.media.base.url="https://dev.open-sunbird.org"
+plugin.media.base.url="https://dev.open-sunbird.org"
+
+# Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+# Elasticsearch properties
+search.es_conn_info="localhost:9200"
+search.fields.query=["name^100","title^100","lemma^100","code^100","tags^100","domain","subject","description^10","keywords^25","ageGroup^10","filter^10","theme^10","genre^10","objects^25","contentType^100","language^200","teachingMode^25","skills^10","learningObjective^10","curriculum^100","gradeLevel^100","developer^100","attributions^10","owner^50","text","words","releaseNotes"]
+search.fields.date=["lastUpdatedOn","createdOn","versionDate","lastSubmittedOn","lastPublishedOn"]
+search.batch.size=500
+search.connection.timeout=30
+platform-api-url="http://localhost:8080/language-service"
+MAX_ITERATION_COUNT_FOR_SAMZA_JOB=2
+
+
+# DIAL Code Configuration
+dialcode.keyspace.name="dialcode_store"
+dialcode.keyspace.table="dial_code"
+dialcode.max_count=1000
+
+# System Configuration
+system.config.keyspace.name="dialcode_store"
+system.config.table="system_config"
+
+#Publisher Configuration
+publisher.keyspace.name="dialcode_store"
+publisher.keyspace.table="publisher"
+
+#DIAL Code Generator Configuration
+dialcode.strip.chars="0"
+dialcode.length=6.0
+dialcode.large.prime_number=1679979167
+
+#DIAL Code ElasticSearch Configuration
+dialcode.index=true
+dialcode.object_type="DialCode"
+
+framework.max_term_creation_limit=200
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+# Kafka configuration details
+kafka.topics.instruction="local.learning.job.request"
+kafka.urls="localhost:9092"
+
+#Youtube Standard Licence Validation
+learning.content.youtube.validate.license=true
+learning.content.youtube.application.name=fetch-youtube-license
+youtube.license.regex.pattern=["\\?vi?=([^&]*)", "watch\\?.*v=([^&]*)", "(?:embed|vi?)/([^/?]*)","^([A-Za-z0-9\\-\\_]*)"]
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+telemetry.search.topn=5
+
+installation.id=ekstep
+
+
+channel.default="in.ekstep"
+
+# DialCode Link API Config
+learning.content.link_dialcode_validation=true
+dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/search"
+dialcode.api.authorization=auth_key
+
+# Language-Code Configuration
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+
+# Kafka send event to topic enable
+kafka.topic.send.enable=false
+
+learning.valid_license=["creativeCommon"]
+learning.service_provider=["youtube"]
+
+stream.mime.type=video/mp4
+compositesearch.index.name="compositesearch"
+
+hierarchy.keyspace.name=hierarchy_store
+content.hierarchy.table=content_hierarchy
+framework.hierarchy.table=framework_hierarchy
+
+# Kafka topic for definition update event.
+kafka.topic.system.command="dev.system.command"
+
+learning.reserve_dialcode.content_type=["TextBook"]
+# restrict.metadata.objectTypes=["Content", "ContentImage", "AssessmentItem", "Channel", "Framework", "Category", "CategoryInstance", "Term"]
+
+#restrict.metadata.objectTypes="Content,ContentImage"
+
+publish.collection.fullecar.disable=true
+
+# Consistency Level for Multi Node Cassandra cluster
+cassandra.lp.consistency.level=QUORUM
+
+
+
+
+content.nested.fields="badgeAssertions,targets,badgeAssociations"
+
+content.cache.ttl=86400
+content.cache.enable=true
+collection.cache.enable=true
+content.discard.status=["Draft","FlagDraft"]
+
+framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
+framework.cache.ttl=86400
+framework.cache.read=true
+
+
+# Max size(width/height) of thumbnail in pixels
+max.thumbnail.size.pixels=150
+
+schema.base_path="../../schemas/"
+content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"]
+
+collection.keyspace = "hierarchy_store"
+content.keyspace = "content_store"
+
+collection.image.migration.enabled=true
+
+
+
+# This is added to handle large artifacts sizes differently
+content.artifact.size.for_online=209715200
+
+contentTypeToPrimaryCategory {
+ ClassroomTeachingVideo: "Explanation Content"
+ ConceptMap: "Learning Resource"
+ Course: "Course"
+ CuriosityQuestionSet: "Practice Question Set"
+ eTextBook: "eTextbook"
+ ExperientialResource: "Learning Resource"
+ ExplanationResource: "Explanation Content"
+ ExplanationVideo: "Explanation Content"
+ FocusSpot: "Teacher Resource"
+ LearningOutcomeDefinition: "Teacher Resource"
+ MarkingSchemeRubric: "Teacher Resource"
+ PedagogyFlow: "Teacher Resource"
+ PracticeQuestionSet: "Practice Question Set"
+ PracticeResource: "Practice Question Set"
+ SelfAssess: "Course Assessment"
+ TeachingMethod: "Teacher Resource"
+ TextBook: "Digital Textbook"
+ Collection: "Content Playlist"
+ ExplanationReadingMaterial: "Learning Resource"
+ LearningActivity: "Learning Resource"
+ LessonPlan: "Content Playlist"
+ LessonPlanResource: "Teacher Resource"
+ PreviousBoardExamPapers: "Learning Resource"
+ TVLesson: "Explanation Content"
+ OnboardingResource: "Learning Resource"
+ ReadingMaterial: "Learning Resource"
+ Template: "Template"
+ Asset: "Asset"
+ Plugin: "Plugin"
+ LessonPlanUnit: "Lesson Plan Unit"
+ CourseUnit: "Course Unit"
+ TextBookUnit: "Textbook Unit"
+}
+
+resourceTypeToPrimaryCategory {
+ Learn: "Learning Resource"
+ Read: "Learning Resource"
+ Practice: "Learning Resource"
+ Teach: "Teacher Resource"
+ Test: "Learning Resource"
+ Experiment: "Learning Resource"
+ LessonPlan: "Teacher Resource"
+}
+
+mimeTypeToPrimaryCategory {
+ "application/vnd.ekstep.h5p-archive": ["Learning Resource"]
+ "application/vnd.ekstep.html-archive": ["Learning Resource"]
+ "application/vnd.android.package-archive": ["Learning Resource"]
+ "video/webm": ["Explanation Content"]
+ "video/x-youtube": ["Explanation Content"]
+ "video/mp4": ["Explanation Content"]
+ "application/pdf": ["Learning Resource", "Teacher Resource"]
+ "application/epub": ["Learning Resource", "Teacher Resource"]
+ "application/vnd.ekstep.ecml-archive": ["Learning Resource", "Teacher Resource"]
+ "text/x-url": ["Learnin Resource", "Teacher Resource"]
+}
+
+objectcategorydefinition.keyspace=category_store
diff --git a/learning-api/hierarchy-manager/src/test/resources/cassandra-unit.yaml b/assessment-api/qs-hierarchy-manager/src/test/resources/cassandra-unit.yaml
similarity index 100%
rename from learning-api/hierarchy-manager/src/test/resources/cassandra-unit.yaml
rename to assessment-api/qs-hierarchy-manager/src/test/resources/cassandra-unit.yaml
diff --git a/learning-api/hierarchy-manager/src/test/resources/logback.xml b/assessment-api/qs-hierarchy-manager/src/test/resources/logback.xml
similarity index 100%
rename from learning-api/hierarchy-manager/src/test/resources/logback.xml
rename to assessment-api/qs-hierarchy-manager/src/test/resources/logback.xml
diff --git a/learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
similarity index 57%
rename from learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
rename to assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
index 346b3c745..16458c14e 100644
--- a/learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
+++ b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
@@ -9,18 +9,25 @@ import org.neo4j.graphdb.GraphDatabaseService
import org.neo4j.graphdb.factory.GraphDatabaseFactory
import org.neo4j.graphdb.factory.GraphDatabaseSettings.Connector.ConnectorType
import org.neo4j.kernel.configuration.BoltConnector
-import org.scalatest.{AsyncFlatSpec, BeforeAndAfterAll, Matchers}
+import org.scalatest.{AsyncFlatSpec, BeforeAndAfterAll, BeforeAndAfterEach, Matchers}
import org.sunbird.cassandra.CassandraConnector
import org.sunbird.common.Platform
-class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll {
+class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach{
var graphDb: GraphDatabaseService = null
var session: Session = null
private val script_1 = "CREATE KEYSPACE IF NOT EXISTS content_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
private val script_2 = "CREATE TABLE IF NOT EXISTS content_store.content_data (content_id text, last_updated_on timestamp,body blob,oldBody blob,stageIcons blob,PRIMARY KEY (content_id));"
-
+ private val script_5 = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val script_6 = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text, objectmetadata map, forms map ,PRIMARY KEY (identifier));"
+ private val script_7 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_8 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});"
+ private val script_9 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_content_all',{'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});"
+ private val script_10 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_11 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_12 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
def setUpEmbeddedNeo4j(): Unit = {
if(null == graphDb) {
@@ -72,13 +79,14 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll {
}
override def beforeAll(): Unit = {
+ tearEmbeddedNeo4JSetup()
setUpEmbeddedNeo4j()
setUpEmbeddedCassandra()
- executeCassandraQuery(script_1, script_2)
+ executeCassandraQuery(script_1, script_2, script_5, script_6, script_7, script_8, script_9, script_10, script_11, script_12)
}
override def afterAll(): Unit = {
- deleteEmbeddedNeo4j(new File(Platform.config.getString("graph.dir")))
+ tearEmbeddedNeo4JSetup()
if(null != session && !session.isClosed)
session.close()
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra()
@@ -102,6 +110,6 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll {
}
def createRelationData(): Unit = {
- graphDb.execute("UNWIND [{identifier:\"Num:C3:SC2\",code:\"Num:C3:SC2\",keywords:[\"Subconcept\",\"Class 3\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",subject:\"numeracy\",channel:\"in.ekstep\",description:\"Multiplication\",versionKey:\"1484389136575\",gradeLevel:[\"Grade 3\",\"Grade 4\"],IL_FUNC_OBJECT_TYPE:\"Concept\",name:\"Multiplication\",lastUpdatedOn:\"2016-06-15T17:15:45.951+0000\",IL_UNIQUE_ID:\"Num:C3:SC2\",status:\"Live\"}, {code:\"31d521da-61de-4220-9277-21ca7ce8335c\",previewUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",downloadUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",channel:\"in.ekstep\",language:[\"English\"],variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790848197_do_11232724509261824014_2.0_spine.ecar\\\",\\\"size\\\":890.0}}\",mimeType:\"application/pdf\",streamingUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",idealScreenSize:\"normal\",createdOn:\"2017-09-07T13:24:20.720+0000\",contentDisposition:\"inline\",artifactUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",contentEncoding:\"identity\",lastUpdatedOn:\"2017-09-07T13:25:53.595+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2017-09-07T13:27:28.417+0000\",contentType:\"Resource\",lastUpdatedBy:\"Ekstep\",audience:[\"Learner\"],visibility:\"Default\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",consumerId:\"e84015d2-a541-4c07-a53f-e31d4553312b\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"Ekstep\",pkgVersion:2,versionKey:\"1504790848417\",license:\"Creative Commons Attribution (CC BY)\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",size:4864851,lastPublishedOn:\"2017-09-07T13:27:27.410+0000\",createdBy:\"390\",compatibilityLevel:4,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Untitled Content\",publisher:\"EkStep\",IL_UNIQUE_ID:\"do_11232724509261824014\",status:\"Live\",resourceType:[\"Study material\"]}] as row CREATE (n:domain) SET n += row")
+ graphDb.execute("UNWIND [{identifier:\"Num:C3:SC2\",code:\"Num:C3:SC2\",keywords:[\"Subconcept\",\"Class 3\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",subject:\"numeracy\",channel:\"in.ekstep\",description:\"Multiplication\",versionKey:\"1484389136575\",gradeLevel:[\"Grade 3\",\"Grade 4\"],IL_FUNC_OBJECT_TYPE:\"Concept\",name:\"Multiplication\",lastUpdatedOn:\"2016-06-15T17:15:45.951+0000\",IL_UNIQUE_ID:\"Num:C3:SC2\",status:\"Live\"}, {code:\"31d521da-61de-4220-9277-21ca7ce8335c\",previewUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",downloadUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",channel:\"in.ekstep\",language:[\"English\"],variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790848197_do_11232724509261824014_2.0_spine.ecar\\\",\\\"size\\\":890.0}}\",mimeType:\"application/pdf\",streamingUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",idealScreenSize:\"normal\",createdOn:\"2017-09-07T13:24:20.720+0000\",contentDisposition:\"inline\",artifactUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",contentEncoding:\"identity\",lastUpdatedOn:\"2017-09-07T13:25:53.595+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2017-09-07T13:27:28.417+0000\",contentType:\"Resource\",lastUpdatedBy:\"Ekstep\",audience:[\"Student\"],visibility:\"Default\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",consumerId:\"e84015d2-a541-4c07-a53f-e31d4553312b\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"Ekstep\",pkgVersion:2,versionKey:\"1504790848417\",license:\"Creative Commons Attribution (CC BY)\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",size:4864851,lastPublishedOn:\"2017-09-07T13:27:27.410+0000\",createdBy:\"390\",compatibilityLevel:4,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Untitled Content\",publisher:\"EkStep\",IL_UNIQUE_ID:\"do_11232724509261824014\",status:\"Live\",resourceType:[\"Study material\"]}] as row CREATE (n:domain) SET n += row")
}
}
diff --git a/build/assessment-service/Dockerfile b/build/assessment-service/Dockerfile
new file mode 100644
index 000000000..d3b28b2cd
--- /dev/null
+++ b/build/assessment-service/Dockerfile
@@ -0,0 +1,14 @@
+FROM sunbird/openjdk-java11-alpine:latest
+RUN apk update \
+ && apk add unzip \
+ && apk add curl \
+ && adduser -u 1001 -h /home/sunbird/ -D sunbird \
+ && mkdir -p /home/sunbird
+RUN chown -R sunbird:sunbird /home/sunbird
+USER sunbird
+COPY ./assessment-api/assessment-service/target/assessment-service-1.0-SNAPSHOT-dist.zip /home/sunbird/
+RUN unzip /home/sunbird/assessment-service-1.0-SNAPSHOT-dist.zip -d /home/sunbird/
+RUN rm /home/sunbird/assessment-service-1.0-SNAPSHOT-dist.zip
+COPY --chown=sunbird ./schemas /home/sunbird/assessment-service-1.0-SNAPSHOT/schemas
+WORKDIR /home/sunbird/
+CMD java -XX:+PrintFlagsFinal $JAVA_OPTIONS -cp '/home/sunbird/assessment-service-1.0-SNAPSHOT/lib/*' -Dconfig.file=/home/sunbird/assessment-service-1.0-SNAPSHOT/config/application.conf -Dlogger.file=/home/sunbird/assessment-service-1.0-SNAPSHOT/config/logback.xml play.core.server.ProdServerStart /home/sunbird/assessment-service-1.0-SNAPSHOT
diff --git a/build/assessment-service/Jenkinsfile b/build/assessment-service/Jenkinsfile
new file mode 100644
index 000000000..ebc9f1a63
--- /dev/null
+++ b/build/assessment-service/Jenkinsfile
@@ -0,0 +1,49 @@
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+
+ cleanWs()
+ checkout scm
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ build_tag = sh(script: "echo " + params.github_release_tag.split('/')[-1] + "_" + commit_hash + "_" + env.BUILD_NUMBER, returnStdout: true).trim()
+ echo "build_tag: " + build_tag
+
+ stage('Build') {
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+ }
+
+ stage('Package') {
+ dir('assessment-api') {
+ sh 'mvn play2:dist -pl assessment-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"assessment-service"} ${env.NODE_NAME} ${hub_org}")
+ }
+ stage('ArchiveArtifacts') {
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+ }
+ }
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ throw err
+ }
+}
diff --git a/build/assessment-service/auto_build_deploy b/build/assessment-service/auto_build_deploy
new file mode 100644
index 000000000..d3332edb1
--- /dev/null
+++ b/build/assessment-service/auto_build_deploy
@@ -0,0 +1,58 @@
+@Library('deploy-conf') _
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ tag_name = env.JOB_NAME.split("/")[-1]
+ pre_checks()
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+ cleanWs()
+ def scmVars = checkout scm
+ checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]]
+ build_tag = tag_name + "_" + env.BUILD_NUMBER
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ artifact_version = tag_name + "_" + commit_hash
+ echo "build_tag: " + build_tag
+
+ // stage Build
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+// stage Package
+ dir('assessment-api') {
+ sh 'mvn play2:dist -pl assessment-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"assessment-service"} ${env.NODE_NAME} ${hub_org}")
+
+// stage ArchiveArtifacts
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+
+ }
+ currentBuild.result = "SUCCESS"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ auto_build_deploy()
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ throw err
+ }
+}
diff --git a/build.sh b/build/build.sh
similarity index 53%
rename from build.sh
rename to build/build.sh
index a27316911..0e24196e8 100755
--- a/build.sh
+++ b/build/build.sh
@@ -3,9 +3,9 @@
set -eo pipefail
build_tag=$1
-name=content-service
-node=$2
-org=$3
+name=$2
+node=$3
+org=$4
-docker build -f ./Dockerfile --label commitHash=$(git rev-parse --short HEAD) -t ${org}/${name}:${build_tag} .
+docker build -f build/${name}/Dockerfile --label commitHash=$(git rev-parse --short HEAD) -t ${org}/${name}:${build_tag} .
echo {\"image_name\" : \"${name}\", \"image_tag\" : \"${build_tag}\", \"node_name\" : \"$node\"} > metadata.json
diff --git a/build/content-service/Dockerfile b/build/content-service/Dockerfile
new file mode 100644
index 000000000..2e829d061
--- /dev/null
+++ b/build/content-service/Dockerfile
@@ -0,0 +1,14 @@
+FROM sunbird/openjdk-java11-alpine:latest
+RUN apk update \
+ && apk add unzip \
+ && apk add curl \
+ && adduser -u 1001 -h /home/sunbird/ -D sunbird \
+ && mkdir -p /home/sunbird
+RUN chown -R sunbird:sunbird /home/sunbird
+USER sunbird
+COPY ./content-api/content-service/target/content-service-1.0-SNAPSHOT-dist.zip /home/sunbird/
+RUN unzip /home/sunbird/content-service-1.0-SNAPSHOT-dist.zip -d /home/sunbird/
+RUN rm /home/sunbird/content-service-1.0-SNAPSHOT-dist.zip
+COPY --chown=sunbird ./schemas /home/sunbird/content-service-1.0-SNAPSHOT/schemas
+WORKDIR /home/sunbird/
+CMD java -XX:+PrintFlagsFinal $JAVA_OPTIONS -cp '/home/sunbird/content-service-1.0-SNAPSHOT/lib/*' -Dconfig.file=/home/sunbird/content-service-1.0-SNAPSHOT/config/application.conf -Dlogger.file=/home/sunbird/content-service-1.0-SNAPSHOT/config/logback.xml play.core.server.ProdServerStart /home/sunbird/content-service-1.0-SNAPSHOT
diff --git a/build/content-service/Jenkinsfile b/build/content-service/Jenkinsfile
new file mode 100644
index 000000000..6909c139c
--- /dev/null
+++ b/build/content-service/Jenkinsfile
@@ -0,0 +1,50 @@
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+
+ cleanWs()
+ checkout scm
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ build_tag = sh(script: "echo " + params.github_release_tag.split('/')[-1] + "_" + commit_hash + "_" + env.BUILD_NUMBER, returnStdout: true).trim()
+ echo "build_tag: " + build_tag
+
+ stage('Build') {
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ }
+
+ stage('Package') {
+ dir('content-api') {
+ sh 'mvn play2:dist -pl content-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"content-service"} ${env.NODE_NAME} ${hub_org}")
+ }
+ stage('ArchiveArtifacts') {
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+ }
+ }
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ throw err
+ }
+}
\ No newline at end of file
diff --git a/build/content-service/auto_build_deploy b/build/content-service/auto_build_deploy
new file mode 100644
index 000000000..1fa11bf83
--- /dev/null
+++ b/build/content-service/auto_build_deploy
@@ -0,0 +1,58 @@
+@Library('deploy-conf') _
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ tag_name = env.JOB_NAME.split("/")[-1]
+ pre_checks()
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+ cleanWs()
+ def scmVars = checkout scm
+ checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]]
+ build_tag = tag_name + "_" + env.BUILD_NUMBER
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ artifact_version = tag_name + "_" + commit_hash
+ echo "build_tag: " + build_tag
+
+ // stage Build
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ // stage Package
+ dir('content-api') {
+ sh 'mvn play2:dist -pl content-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"content-service"} ${env.NODE_NAME} ${hub_org}")
+
+ // stage ArchiveArtifacts
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+
+ }
+ currentBuild.result = "SUCCESS"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ auto_build_deploy()
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ throw err
+ }
+}
diff --git a/build/search-service/Dockerfile b/build/search-service/Dockerfile
new file mode 100644
index 000000000..be9830e24
--- /dev/null
+++ b/build/search-service/Dockerfile
@@ -0,0 +1,14 @@
+FROM sunbird/openjdk-java11-alpine:latest
+RUN apk update \
+ && apk add unzip \
+ && apk add curl \
+ && adduser -u 1001 -h /home/sunbird/ -D sunbird \
+ && mkdir -p /home/sunbird
+RUN chown -R sunbird:sunbird /home/sunbird
+USER sunbird
+COPY ./search-api/search-service/target/search-service-1.0-SNAPSHOT-dist.zip /home/sunbird/
+RUN unzip /home/sunbird/search-service-1.0-SNAPSHOT-dist.zip -d /home/sunbird/
+RUN rm /home/sunbird/search-service-1.0-SNAPSHOT-dist.zip
+COPY --chown=sunbird ./schemas /home/sunbird/search-service-1.0-SNAPSHOT/schemas
+WORKDIR /home/sunbird/
+CMD java -XX:+PrintFlagsFinal $JAVA_OPTIONS -cp '/home/sunbird/search-service-1.0-SNAPSHOT/lib/*' -Dconfig.file=/home/sunbird/search-service-1.0-SNAPSHOT/config/application.conf -Dlogger.file=/home/sunbird/search-service-1.0-SNAPSHOT/config/logback.xml play.core.server.ProdServerStart /home/sunbird/search-service-1.0-SNAPSHOT
diff --git a/build/search-service/Jenkinsfile b/build/search-service/Jenkinsfile
new file mode 100644
index 000000000..a565e7da3
--- /dev/null
+++ b/build/search-service/Jenkinsfile
@@ -0,0 +1,50 @@
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+
+ cleanWs()
+ checkout scm
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ build_tag = sh(script: "echo " + params.github_release_tag.split('/')[-1] + "_" + commit_hash + "_" + env.BUILD_NUMBER, returnStdout: true).trim()
+ echo "build_tag: " + build_tag
+
+ stage('Build') {
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ }
+
+ stage('Package') {
+ dir('search-api') {
+ sh 'mvn play2:dist -pl search-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"search-service"} ${env.NODE_NAME} ${hub_org}")
+ }
+ stage('ArchiveArtifacts') {
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+ }
+ }
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ throw err
+ }
+}
diff --git a/build/search-service/auto_build_deploy b/build/search-service/auto_build_deploy
new file mode 100644
index 000000000..61b5bab08
--- /dev/null
+++ b/build/search-service/auto_build_deploy
@@ -0,0 +1,57 @@
+@Library('deploy-conf') _
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ tag_name = env.JOB_NAME.split("/")[-1]
+ pre_checks()
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+ cleanWs()
+ def scmVars = checkout scm
+ checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]]
+ build_tag = tag_name + "_" + env.BUILD_NUMBER
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ artifact_version = tag_name + "_" + commit_hash
+ echo "build_tag: " + build_tag
+
+ // stage Build
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ // stage Package
+ dir('search-api') {
+ sh 'mvn play2:dist -pl search-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"search-service"} ${env.NODE_NAME} ${hub_org}")
+
+ // stage('ArchiveArtifacts')
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+ }
+ currentBuild.result = "SUCCESS"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ auto_build_deploy()
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ throw err
+ }
+}
diff --git a/build/taxonomy-service/Dockerfile b/build/taxonomy-service/Dockerfile
new file mode 100644
index 000000000..89dae255c
--- /dev/null
+++ b/build/taxonomy-service/Dockerfile
@@ -0,0 +1,14 @@
+FROM sunbird/openjdk-java11-alpine:latest
+RUN apk update \
+ && apk add unzip \
+ && apk add curl \
+ && adduser -u 1001 -h /home/sunbird/ -D sunbird \
+ && mkdir -p /home/sunbird
+RUN chown -R sunbird:sunbird /home/sunbird
+USER sunbird
+COPY ./taxonomy-api/taxonomy-service/target/taxonomy-service-1.0-SNAPSHOT-dist.zip /home/sunbird/
+RUN unzip /home/sunbird/taxonomy-service-1.0-SNAPSHOT-dist.zip -d /home/sunbird/
+RUN rm /home/sunbird/taxonomy-service-1.0-SNAPSHOT-dist.zip
+COPY --chown=sunbird ./schemas /home/sunbird/taxonomy-service-1.0-SNAPSHOT/schemas
+WORKDIR /home/sunbird/
+CMD java -XX:+PrintFlagsFinal $JAVA_OPTIONS -cp '/home/sunbird/taxonomy-service-1.0-SNAPSHOT/lib/*' -Dconfig.file=/home/sunbird/taxonomy-service-1.0-SNAPSHOT/config/application.conf -Dlogger.file=/home/sunbird/taxonomy-service-1.0-SNAPSHOT/config/logback.xml play.core.server.ProdServerStart /home/sunbird/taxonomy-service-1.0-SNAPSHOT
diff --git a/build/taxonomy-service/Jenkinsfile b/build/taxonomy-service/Jenkinsfile
new file mode 100644
index 000000000..453a603f6
--- /dev/null
+++ b/build/taxonomy-service/Jenkinsfile
@@ -0,0 +1,50 @@
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+
+ cleanWs()
+ checkout scm
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ build_tag = sh(script: "echo " + params.github_release_tag.split('/')[-1] + "_" + commit_hash + "_" + env.BUILD_NUMBER, returnStdout: true).trim()
+ echo "build_tag: " + build_tag
+
+ stage('Build') {
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ }
+
+ stage('Package') {
+ dir('taxonomy-api') {
+ sh 'mvn play2:dist -pl taxonomy-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"taxonomy-service"} ${env.NODE_NAME} ${hub_org}")
+ }
+ stage('ArchiveArtifacts') {
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+ }
+ }
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ throw err
+ }
+}
diff --git a/build/taxonomy-service/auto_build_deploy b/build/taxonomy-service/auto_build_deploy
new file mode 100644
index 000000000..30fea1e4d
--- /dev/null
+++ b/build/taxonomy-service/auto_build_deploy
@@ -0,0 +1,59 @@
+@Library('deploy-conf') _
+node('build-slave') {
+ try {
+ String ANSI_GREEN = "\u001B[32m"
+ String ANSI_NORMAL = "\u001B[0m"
+ String ANSI_BOLD = "\u001B[1m"
+ String ANSI_RED = "\u001B[31m"
+ String ANSI_YELLOW = "\u001B[33m"
+
+ ansiColor('xterm') {
+ withEnv(["JAVA_HOME=${JAVA11_HOME}"]) {
+ stage('Checkout') {
+ tag_name = env.JOB_NAME.split("/")[-1]
+ pre_checks()
+ if (!env.hub_org) {
+ println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
+ error 'Please resolve the errors and rerun..'
+ } else
+ println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
+ }
+ cleanWs()
+ def scmVars = checkout scm
+ checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]]
+ build_tag = tag_name + "_" + env.BUILD_NUMBER
+ commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
+ artifact_version = tag_name + "_" + commit_hash
+ echo "build_tag: " + build_tag
+ echo "build_tag: " + build_tag
+
+ // stage Build
+ env.NODE_ENV = "build"
+ print "Environment will be : ${env.NODE_ENV}"
+ sh 'mvn clean install -DskipTests=true '
+
+ // stage Package
+ dir('taxonomy-api') {
+ sh 'mvn play2:dist -pl taxonomy-service'
+ }
+ sh('chmod 777 build/build.sh')
+ sh("build/build.sh ${build_tag} ${"taxonomy-service"} ${env.NODE_NAME} ${hub_org}")
+
+// stage ArchiveArtifacts
+ archiveArtifacts "metadata.json"
+ currentBuild.description = "${build_tag}"
+
+ }
+ currentBuild.result = "SUCCESS"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ auto_build_deploy()
+ }
+ }
+ catch (err) {
+ currentBuild.result = "FAILURE"
+ slack_notify(currentBuild.result, tag_name)
+ email_notify()
+ throw err
+ }
+}
diff --git a/content-api/content-actors/pom.xml b/content-api/content-actors/pom.xml
new file mode 100644
index 000000000..a429dc8f9
--- /dev/null
+++ b/content-api/content-actors/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+ content-api
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ content-actors
+
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.sunbird
+ actor-core
+ 1.0-SNAPSHOT
+
+
+ org.sunbird
+ kafka-client
+ 1.0-SNAPSHOT
+
+
+ org.sunbird
+ graph-engine_2.11
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.sunbird
+ mimetype-manager
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.sunbird
+ import-manager
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ 3.0.8
+ test
+
+
+ com.mashape.unirest
+ unirest-java
+ 1.4.9
+
+
+ org.sunbird
+ hierarchy-manager
+ 1.0-SNAPSHOT
+
+
+ org.scalamock
+ scalamock_${scala.maj.version}
+ 4.4.0
+ test
+
+
+ com.typesafe.akka
+ akka-testkit_${scala.maj.version}
+ 2.5.22
+ test
+
+
+
+
+ src/main/scala
+ src/test/scala
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.4.0
+
+ ${scala.version}
+ false
+
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+ scala-test-compile
+ process-test-resources
+
+ testCompile
+
+
+
+
+
+ org.scalatest
+ scalatest-maven-plugin
+ 2.0.0
+
+
+ test
+ test
+
+ test
+
+
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/channel/actors/ChannelActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/channel/actors/ChannelActor.scala
new file mode 100644
index 000000000..6c1f0358b
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/channel/actors/ChannelActor.scala
@@ -0,0 +1,80 @@
+package org.sunbird.channel.actors
+
+import java.util
+
+import javax.inject.Inject
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.util.RequestUtil
+import org.sunbird.channel.managers.ChannelManager
+import org.sunbird.common.exception.ClientException
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.common.Platform
+
+import scala.concurrent.{ExecutionContext, Future}
+import org.apache.commons.collections4.CollectionUtils
+import org.sunbird.graph.OntologyEngineContext
+
+class ChannelActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ val suggestFrameworks = if(Platform.config.hasPath("channel.fetch.suggested_frameworks")) Platform.config.getBoolean("channel.fetch.suggested_frameworks") else true
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case "createChannel" => create(request)
+ case "readChannel" => read(request)
+ case "updateChannel" => update(request)
+ case "retireChannel" => retire(request)
+ case _ => ERROR(request.getOperation)
+ }
+
+ }
+
+ def create(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ if (!request.getRequest.containsKey("code"))
+ throw new ClientException("ERR_CODE_IS_REQUIRED", "Code is required for creating a channel")
+ request.getRequest.put("identifier", request.getRequest.get("code").asInstanceOf[String])
+ ChannelManager.validateTranslationMap(request)
+ ChannelManager.validateObjectCategory(request)
+ DataNode.create(request).map(node => {
+ ChannelManager.channelLicenseCache(request, node.getIdentifier)
+ ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier)
+ })
+ }
+
+ def read(request: Request): Future[Response] = {
+ DataNode.read(request).map(node => {
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, null, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ if (suggestFrameworks && CollectionUtils.isEmpty(metadata.getOrDefault("frameworks", new util.ArrayList[AnyRef]()).asInstanceOf[util.List[AnyRef]])) {
+ val frameworkList = ChannelManager.getAllFrameworkList()
+ if (!frameworkList.isEmpty) metadata.put("suggested_frameworks", frameworkList)
+ }
+ ChannelManager.setPrimaryAndAdditionCategories(metadata)
+ ResponseHandler.OK.put("channel", metadata)
+ })
+ }
+
+ def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ ChannelManager.validateTranslationMap(request)
+ ChannelManager.validateObjectCategory(request)
+ request.getRequest.put("status", "Live")
+ DataNode.update(request).map(node => {
+ val identifier: String = node.getIdentifier
+ ChannelManager.channelLicenseCache(request, identifier)
+ ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier)
+ })
+ }
+
+ def retire(request: Request): Future[Response] = {
+ request.getRequest.put("status", "Retired")
+ DataNode.update(request).map(node => {
+ val identifier: String = node.getIdentifier
+ ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier)
+ })
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/channel/managers/ChannelManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/channel/managers/ChannelManager.scala
new file mode 100644
index 000000000..ae9d69050
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/channel/managers/ChannelManager.scala
@@ -0,0 +1,153 @@
+package org.sunbird.channel.managers
+
+import java.util
+import java.util.Optional
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.util.{ChannelConstants, HttpUtil}
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.exception.{ClientException, ServerException}
+import org.sunbird.common.Platform
+import com.mashape.unirest.http.HttpResponse
+import com.mashape.unirest.http.Unirest
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.JsonUtils
+
+import scala.collection.JavaConverters._
+import scala.collection.JavaConversions._
+import scala.collection.mutable.ListBuffer
+
+object ChannelManager {
+
+ val CONTENT_PRIMARY_CATEGORIES: util.List[String] = Platform.getStringList("channel.content.primarycategories", new util.ArrayList[String]())
+ val COLLECTION_PRIMARY_CATEGORIES: util.List[String] = Platform.getStringList("channel.collection.primarycategories", new util.ArrayList[String]())
+ val ASSET_PRIMARY_CATEGORIES: util.List[String] = Platform.getStringList("channel.asset.primarycategories", new util.ArrayList[String]())
+ val CONTENT_ADDITIONAL_CATEGORIES: util.List[String] = Platform.getStringList("channel.content.additionalcategories", new util.ArrayList[String]())
+ val COLLECTION_ADDITIONAL_CATEGORIES: util.List[String] = Platform.getStringList("channel.collection.additionalcategories", new util.ArrayList[String]())
+ val ASSET_ADDITIONAL_CATEGORIES: util.List[String] = Platform.getStringList("channel.asset.additionalcategories", new util.ArrayList[String]())
+ implicit val httpUtil: HttpUtil = new HttpUtil
+
+ def channelLicenseCache(request: Request, identifier: String): Unit = {
+ if (request.getRequest.containsKey(ChannelConstants.DEFAULT_LICENSE))
+ RedisCache.set(ChannelConstants.CHANNEL_LICENSE_CACHE_PREFIX + identifier + ChannelConstants.CHANNEL_LICENSE_CACHE_SUFFIX, request.getRequest.get(ChannelConstants.DEFAULT_LICENSE).asInstanceOf[String], 0)
+ }
+
+ def getAllFrameworkList(): util.List[util.Map[String, AnyRef]] = {
+ val url: String = Platform.getString("composite.search.url", "https://dev.sunbirded.org/action/composite/v3/search")
+ val httpResponse: HttpResponse[String] = Unirest.post(url).header("Content-Type", "application/json").body("""{"request":{"filters":{"objectType":"Framework","status":"Live"},"fields":["name","code","objectType","identifier"]}}""").asString
+ if (200 != httpResponse.getStatus)
+ throw new ServerException("ERR_FETCHING_FRAMEWORK", "Error while fetching framework.")
+ val response: Response = JsonUtils.deserialize(httpResponse.getBody, classOf[Response])
+ response.getResult.getOrDefault("Framework", new util.ArrayList[util.Map[String, AnyRef]]()).asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ }
+
+ def validateTranslationMap(request: Request) = {
+ val translations: util.Map[String, AnyRef] = Optional.ofNullable(request.get("translations").asInstanceOf[util.HashMap[String, AnyRef]]).orElse(new util.HashMap[String, AnyRef]())
+ if (translations.isEmpty) request.getRequest.remove("translations")
+ else {
+ val languageCodes = Platform.getStringList("platform.language.codes", new util.ArrayList[String]())
+ if (translations.asScala.exists(entry => !languageCodes.contains(entry._1)))
+ throw new ClientException("ERR_INVALID_LANGUAGE_CODE", "Please Provide Valid Language Code For translations. Valid Language Codes are : " + languageCodes)
+ }
+ }
+
+ def validateObjectCategory(request: Request) = {
+ if (!util.Collections.disjoint(request.getRequest.keySet(), ChannelConstants.categoryKeyList)) {
+ val masterCategoriesList: List[String] = getMasterCategoryList()
+ val errMsg: ListBuffer[String] = ListBuffer()
+ compareWithMasterCategory(request, masterCategoriesList, errMsg)
+ if (errMsg.nonEmpty)
+ throw new ClientException(ChannelConstants.ERR_VALIDATING_PRIMARY_CATEGORY, "Please provide valid : " + errMsg.mkString("[", ",", "]"))
+ }
+ }
+
+ def compareWithMasterCategory(request: Request, masterCat: List[String], errMsg: ListBuffer[String]): Unit = {
+ ChannelConstants.categoryKeyList.map(cat => {
+ if (request.getRequest.containsKey(cat)) {
+ val requestedCategoryList: util.List[String] = getRequestedCategoryList(request, cat)
+ if (!masterCat.containsAll(requestedCategoryList))
+ errMsg += cat
+ }
+ })
+ }
+
+ def getRequestedCategoryList(request: Request, cat: String): util.ArrayList[String] = {
+ try {
+ val requestedList = request.getRequest.get(cat).asInstanceOf[util.ArrayList[String]]
+ if (requestedList.isEmpty)
+ throw new ClientException(ChannelConstants.ERR_VALIDATING_PRIMARY_CATEGORY, "Empty list not allowed for " + cat)
+ requestedList
+ } catch {
+ case e: ClassCastException => {
+ throw new ClientException(ChannelConstants.ERR_VALIDATING_PRIMARY_CATEGORY, "Please provide valid list for " + cat)
+ }
+ case e: ClientException => {
+ throw new ClientException(e.getErrCode, e.getMessage)
+ }
+ case e: Exception => {
+ throw new ServerException(ChannelConstants.ERR_VALIDATING_PRIMARY_CATEGORY, e.getMessage)
+ }
+ }
+ }
+
+ def setPrimaryAndAdditionCategories(metadata: util.Map[String, AnyRef]): Unit = {
+ metadata.putIfAbsent(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, CONTENT_PRIMARY_CATEGORIES)
+ metadata.putIfAbsent(ChannelConstants.COLLECTION_PRIMARY_CATEGORIES, COLLECTION_PRIMARY_CATEGORIES)
+ metadata.putIfAbsent(ChannelConstants.ASSET_PRIMARY_CATEGORIES, ASSET_PRIMARY_CATEGORIES)
+ metadata.putIfAbsent(ChannelConstants.CONTENT_ADDITIONAL_CATEGORIES, CONTENT_ADDITIONAL_CATEGORIES)
+ metadata.putIfAbsent(ChannelConstants.COLLECTION_ADDITIONAL_CATEGORIES, COLLECTION_ADDITIONAL_CATEGORIES)
+ metadata.putIfAbsent(ChannelConstants.ASSET_ADDITIONAL_CATEGORIES, ASSET_ADDITIONAL_CATEGORIES)
+ val primaryCategories = getChannelPrimaryCategories(metadata.get("identifier").asInstanceOf[String])
+ metadata.put("primaryCategories", primaryCategories)
+ val additionalCategories = getAdditionalCategories()
+ metadata.put("additionalCategories", additionalCategories)
+ }
+
+ def getAdditionalCategories()(implicit httpUtil: HttpUtil): java.util.List[String] = {
+ val body = """{"request":{"filters":{"objectType":"ObjectCategory","visibility":["Default"]},"fields":["name","identifier"]}}"""
+ val url: String = Platform.getString("composite.search.url", "https://dev.sunbirded.org/action/composite/v3/search")
+ val httpResponse = httpUtil.post(url, body)
+ if (200 != httpResponse.status) throw new ServerException("ERR_FETCHING_OBJECT_CATEGORY", "Error while fetching object categories for additional category list.")
+ val response: Response = JsonUtils.deserialize(httpResponse.body, classOf[Response])
+ val objectCategoryList: util.List[util.Map[String, AnyRef]] = response.getResult.getOrDefault(ChannelConstants.OBJECT_CATEGORY, new util.ArrayList[util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]]
+ objectCategoryList.asScala.map(cat => cat.get("name").asInstanceOf[String]).asJava
+
+ }
+
+ def getChannelPrimaryCategories(channel: String)(implicit httpUtil: HttpUtil): java.util.List[java.util.Map[String, AnyRef]] = {
+ val globalPCRequest = s"""{"request":{"filters":{"objectType":"ObjectCategoryDefinition", "visibility":["Default"]},"not_exists": "channel","fields":["name","identifier","targetObjectType"]}}"""
+ val globalPrimaryCategories = getPrimaryCategories(globalPCRequest)
+ val channelPCRequest = s"""{"request":{"filters":{"objectType":"ObjectCategoryDefinition", "visibility":["Default"], "channel": "$channel"},"fields":["name","identifier","targetObjectType"]}}"""
+ val channelPrimaryCategories = getPrimaryCategories(channelPCRequest)
+ if (CollectionUtils.isEmpty(channelPrimaryCategories))
+ globalPrimaryCategories
+ else {
+ val idsToIgnore = channelPrimaryCategories.map(cat => cat.get("identifier").asInstanceOf[String])
+ .map(id => id.replace("_"+channel, "_all"))
+ globalPrimaryCategories.filter(cat => {
+ !idsToIgnore.contains(cat.get("identifier").asInstanceOf[String])
+ }) ++ channelPrimaryCategories
+ }
+ }
+
+ private def getPrimaryCategories(body: String)(implicit httpUtil: HttpUtil): java.util.List[java.util.Map[String, AnyRef]] = {
+ val url: String = Platform.getString("composite.search.url", "https://dev.sunbirded.org/action/composite/v3/search")
+ val httpResponse = httpUtil.post(url, body)
+ if (200 != httpResponse.status) throw new ServerException("ERR_FETCHING_OBJECT_CATEGORY_DEFINITION", "Error while fetching primary categories.")
+ val response: Response = JsonUtils.deserialize(httpResponse.body, classOf[Response])
+ val objectCategoryList: util.List[util.Map[String, AnyRef]] = response.getResult.getOrDefault(ChannelConstants.objectCategoryDefinitionKey, new util.ArrayList[util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]]
+ objectCategoryList.asScala.map(cat => (cat - "objectType").asJava).asJava
+ }
+
+ def getMasterCategoryList(): List[String] = {
+ val url: String = Platform.getString("composite.search.url", "https://dev.sunbirded.org/action/composite/v3/search")
+ val httpResponse: HttpResponse[String] = Unirest.post(url).header("Content-Type", "application/json").body("""{"request":{"filters":{"objectType":"ObjectCategory"},"fields":["name"]}}""").asString
+ if (200 != httpResponse.getStatus)
+ throw new ServerException("ERR_FETCHING_OBJECT_CATEGORY", "Error while fetching object category.")
+ val response: Response = JsonUtils.deserialize(httpResponse.getBody, classOf[Response])
+ val objectCategoryList: util.List[util.Map[String, AnyRef]] = response.getResult.getOrDefault(ChannelConstants.OBJECT_CATEGORY, new util.ArrayList[util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]]
+ if (objectCategoryList.isEmpty)
+ throw new ClientException("ERR_NO_MASTER_OBJECT_CATEGORY_DEFINED", "Master category object not present")
+ objectCategoryList.map(a => a.getOrDefault("name", "").asInstanceOf[String]).toList
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AppActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AppActor.scala
new file mode 100644
index 000000000..8bb6e5aea
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AppActor.scala
@@ -0,0 +1,71 @@
+package org.sunbird.content.actors
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.Slug
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.util.RequestUtil
+
+import java.util
+import javax.inject.Inject
+import scala.collection.JavaConverters
+import scala.concurrent.{ExecutionContext, Future}
+
+/***
+ * TODO: rewrite this Actor after merging the Event and EventSet code.
+ */
+class AppActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case "create" => create(request)
+ case "read" => read(request)
+ case "update" => update(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+ def create(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ setIdentifier(request)
+ DataNode.create(request, (node: Node) => node).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ })
+ }
+
+ @throws[Exception]
+ private def read(request: Request): Future[Response] = {
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ if (NodeUtil.isRetired(node)) ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name, "App not found with identifier: " + node.getIdentifier)
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ ResponseHandler.OK.put("app", metadata)
+ })
+ }
+
+ @throws[Exception]
+ private def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier)
+ })
+ }
+
+ private def setIdentifier(request: Request) = {
+ val osType = request.getRequest.getOrDefault("osType", "").asInstanceOf[String]
+ val packageId = request.getRequest.getOrDefault("osMetadata", new util.HashMap[String, AnyRef]())
+ .asInstanceOf[java.util.Map[String, AnyRef]]
+ .getOrDefault("packageId", "").asInstanceOf[String]
+ val identifier = if (StringUtils.isNotBlank(osType) && StringUtils.isNotBlank(packageId)) Slug.makeSlug(s"$osType-$packageId", true) else ""
+ request.getRequest.put("identifier", identifier)
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AssetActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AssetActor.scala
new file mode 100644
index 000000000..974ea7f53
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/AssetActor.scala
@@ -0,0 +1,28 @@
+package org.sunbird.content.actors
+
+import com.google.inject.Singleton
+import javax.inject.Inject
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.content.util.AssetCopyManager
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.util.RequestUtil
+
+import scala.concurrent.{ExecutionContext, Future}
+@Singleton
+class AssetActor @Inject()(implicit oec: OntologyEngineContext, ss: StorageService) extends BaseActor {
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case "copy" => copy(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+ def copy(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ AssetCopyManager.copy(request)
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CategoryActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CategoryActor.scala
new file mode 100644
index 000000000..5c466bebe
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CategoryActor.scala
@@ -0,0 +1,74 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import javax.inject.Inject
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.Slug
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.content.util.CategoryConstants
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.util.RequestUtil
+
+import scala.collection.JavaConverters
+import scala.concurrent.{ExecutionContext, Future}
+
+class CategoryActor @Inject()(implicit oec: OntologyEngineContext) extends BaseActor {
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case CategoryConstants.CREATE_CATEGORY => create(request)
+ case CategoryConstants.READ_CATEGORY => read(request)
+ case CategoryConstants.UPDATE_CATEGORY => update(request)
+ case CategoryConstants.RETIRE_CATEGORY => retire(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+
+ @throws[Exception]
+ private def create(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ if (request.getRequest.containsKey("identifier")) throw new ClientException("ERR_NAME_SET_AS_IDENTIFIER", "name will be set as identifier")
+ if (request.getRequest.containsKey("name")) request.getRequest.put("identifier", "cat-" + Slug.makeSlug(request.getRequest.get("name").asInstanceOf[String]))
+ DataNode.create(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier)
+ })
+ }
+
+ @throws[Exception]
+ private def read(request: Request): Future[Response] = {
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ if (NodeUtil.isRetired(node)) ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name, "Category not found with identifier: " + node.getIdentifier)
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ ResponseHandler.OK.put("category", metadata)
+ })
+ }
+
+ @throws[Exception]
+ private def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ request.getRequest.put("status", "Live")
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("node_id", node.getIdentifier)
+ .put("identifier", node.getIdentifier)
+ })
+ }
+
+ @throws[Exception]
+ private def retire(request: Request): Future[Response] = {
+ request.getRequest.put("status", "Retired")
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("node_id", node.getIdentifier)
+ .put("identifier", node.getIdentifier)
+ })
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CollectionActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CollectionActor.scala
new file mode 100644
index 000000000..f16b76855
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/CollectionActor.scala
@@ -0,0 +1,27 @@
+package org.sunbird.content.actors
+
+import javax.inject.Inject
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.managers.{HierarchyManager, UpdateHierarchyManager}
+import org.sunbird.utils.HierarchyConstants
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class CollectionActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getContext.put(HierarchyConstants.SCHEMA_NAME, HierarchyConstants.COLLECTION_SCHEMA_NAME)
+ request.getOperation match {
+ case "addHierarchy" => HierarchyManager.addLeafNodesToHierarchy(request)
+ case "removeHierarchy" => HierarchyManager.removeLeafNodesFromHierarchy(request)
+ case "updateHierarchy" => UpdateHierarchyManager.updateHierarchy(request)
+ case "getHierarchy" => HierarchyManager.getHierarchy(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ContentActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ContentActor.scala
new file mode 100644
index 000000000..4d8892a79
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ContentActor.scala
@@ -0,0 +1,208 @@
+package org.sunbird.content.actors
+
+import java.util
+import java.util.concurrent.CompletionException
+import java.io.File
+
+import org.apache.commons.io.FilenameUtils
+import javax.inject.Inject
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.`object`.importer.{ImportConfig, ImportManager}
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.content.util.{AcceptFlagManager, ContentConstants, CopyManager, DiscardManager, FlagManager, RetireManager}
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.{ContentParams, Platform, Slug}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ClientException
+import org.sunbird.content.dial.DIALManager
+import org.sunbird.util.RequestUtil
+import org.sunbird.content.upload.mgr.UploadManager
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+
+import scala.collection.JavaConverters
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+class ContentActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageService) extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+ private lazy val importConfig = getImportConfig()
+ private lazy val importMgr = new ImportManager(importConfig)
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case "createContent" => create(request)
+ case "readContent" => read(request)
+ case "updateContent" => update(request)
+ case "uploadContent" => upload(request)
+ case "retireContent" => retire(request)
+ case "copy" => copy(request)
+ case "uploadPreSignedUrl" => uploadPreSignedUrl(request)
+ case "discardContent" => discard(request)
+ case "flagContent" => flag(request)
+ case "acceptFlag" => acceptFlag(request)
+ case "linkDIALCode" => linkDIALCode(request)
+ case "importContent" => importContent(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+ def create(request: Request): Future[Response] = {
+ populateDefaultersForCreation(request)
+ RequestUtil.restrictProperties(request)
+ DataNode.create(request, dataModifier).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier)
+ .put("versionKey", node.getMetadata.get("versionKey"))
+ })
+ }
+
+ def read(request: Request): Future[Response] = {
+ val responseSchemaName: String = request.getContext.getOrDefault(ContentConstants.RESPONSE_SCHEMA_NAME, "").asInstanceOf[String]
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, node.getObjectType.toLowerCase.replace("image", ""), request.getContext.get("version").asInstanceOf[String])
+ metadata.put("identifier", node.getIdentifier.replace(".img", ""))
+ val response: Response = ResponseHandler.OK
+ if (responseSchemaName.isEmpty) {
+ response.put("content", metadata)
+ }
+ else {
+ response.put(responseSchemaName, metadata)
+ }
+ response
+ })
+ }
+
+ def update(request: Request): Future[Response] = {
+ populateDefaultersForUpdation(request)
+ if (StringUtils.isBlank(request.getRequest.getOrDefault("versionKey", "").asInstanceOf[String])) throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!")
+ RequestUtil.restrictProperties(request)
+ DataNode.update(request, dataModifier).map(node => {
+ val identifier: String = node.getIdentifier.replace(".img", "")
+ ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier)
+ .put("versionKey", node.getMetadata.get("versionKey"))
+ })
+ }
+
+ def upload(request: Request): Future[Response] = {
+ val identifier: String = request.getContext.getOrDefault("identifier", "").asInstanceOf[String]
+ val readReq = new Request(request)
+ readReq.put("identifier", identifier)
+ readReq.put("fields", new util.ArrayList[String])
+ DataNode.read(readReq).map(node => {
+ if (null != node & StringUtils.isNotBlank(node.getObjectType))
+ request.getContext.put("schemaName", node.getObjectType.toLowerCase())
+ UploadManager.upload(request, node)
+ }).flatMap(f => f)
+ }
+
+ def copy(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ CopyManager.copy(request)
+ }
+
+ def uploadPreSignedUrl(request: Request): Future[Response] = {
+ val `type`: String = request.get("type").asInstanceOf[String].toLowerCase()
+ val fileName: String = request.get("fileName").asInstanceOf[String]
+ val filePath: String = request.getRequest.getOrDefault("filePath","").asInstanceOf[String]
+ .replaceAll("^/+|/+$", "")
+ val identifier: String = request.get("identifier").asInstanceOf[String]
+ validatePreSignedUrlRequest(`type`, fileName, filePath)
+ DataNode.read(request).map(node => {
+ val objectKey = if (StringUtils.isEmpty(filePath)) "content" + File.separator + `type` + File.separator + identifier + File.separator + Slug.makeSlug(fileName, true)
+ else filePath + File.separator + "content" + File.separator + `type` + File.separator + identifier + File.separator + Slug.makeSlug(fileName, true)
+ val expiry = Platform.config.getString("cloud_storage.upload.url.ttl")
+ val preSignedURL = ss.getSignedURL(objectKey, Option.apply(expiry.toInt), Option.apply("w"))
+ ResponseHandler.OK().put("identifier", identifier).put("pre_signed_url", preSignedURL)
+ .put("url_expiry", expiry)
+ }) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def retire(request: Request): Future[Response] = {
+ RetireManager.retire(request)
+ }
+ def discard(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ DiscardManager.discard(request)
+ }
+
+ def flag(request: Request): Future[Response] = {
+ FlagManager.flag(request)
+ }
+
+ def acceptFlag(request: Request): Future[Response] = {
+ AcceptFlagManager.acceptFlag(request)
+ }
+
+ def linkDIALCode(request: Request): Future[Response] = DIALManager.link(request)
+
+ def importContent(request: Request): Future[Response] = importMgr.importObject(request)
+
+ def populateDefaultersForCreation(request: Request) = {
+ setDefaultsBasedOnMimeType(request, ContentParams.create.name)
+ setDefaultLicense(request)
+ }
+
+ private def setDefaultLicense(request: Request): Unit = {
+ if (StringUtils.isEmpty(request.getRequest.getOrDefault("license", "").asInstanceOf[String])) {
+ val cacheKey = "channel_" + request.getRequest.getOrDefault("channel", "").asInstanceOf[String] + "_license"
+ val defaultLicense = RedisCache.get(cacheKey, null, 0)
+ if (StringUtils.isNotEmpty(defaultLicense)) request.getRequest.put("license", defaultLicense)
+ else System.out.println("Default License is not available for channel: " + request.getRequest.getOrDefault("channel", "").asInstanceOf[String])
+ }
+ }
+
+ def populateDefaultersForUpdation(request: Request) = {
+ if (request.getRequest.containsKey(ContentParams.body.name)) request.put(ContentParams.artifactUrl.name, null)
+ }
+
+ private def setDefaultsBasedOnMimeType(request: Request, operation: String): Unit = {
+ val mimeType = request.get(ContentParams.mimeType.name).asInstanceOf[String]
+ if (StringUtils.isNotBlank(mimeType) && operation.equalsIgnoreCase(ContentParams.create.name)) {
+ if (StringUtils.equalsIgnoreCase("application/vnd.ekstep.plugin-archive", mimeType)) {
+ val code = request.get(ContentParams.code.name).asInstanceOf[String]
+ if (null == code || StringUtils.isBlank(code)) throw new ClientException("ERR_PLUGIN_CODE_REQUIRED", "Unique code is mandatory for plugins")
+ request.put(ContentParams.identifier.name, request.get(ContentParams.code.name))
+ }
+ else request.put(ContentParams.osId.name, "org.ekstep.quiz.app")
+ if (mimeType.endsWith("archive") || mimeType.endsWith("vnd.ekstep.content-collection") || mimeType.endsWith("epub")) request.put(ContentParams.contentEncoding.name, ContentParams.gzip.name)
+ else request.put(ContentParams.contentEncoding.name, ContentParams.identity.name)
+ if (mimeType.endsWith("youtube") || mimeType.endsWith("x-url")) request.put(ContentParams.contentDisposition.name, ContentParams.online.name)
+ else request.put(ContentParams.contentDisposition.name, ContentParams.inline.name)
+ }
+ }
+
+ private def validatePreSignedUrlRequest(`type`: String, fileName: String, filePath: String): Unit = {
+ if (StringUtils.isEmpty(fileName))
+ throw new ClientException("ERR_CONTENT_BLANK_FILE_NAME", "File name is blank")
+ if (StringUtils.isBlank(FilenameUtils.getBaseName(fileName)) || StringUtils.length(Slug.makeSlug(fileName, true)) > 256)
+ throw new ClientException("ERR_CONTENT_INVALID_FILE_NAME", "Please Provide Valid File Name.")
+ if (!preSignedObjTypes.contains(`type`))
+ throw new ClientException("ERR_INVALID_PRESIGNED_URL_TYPE", "Invalid pre-signed url type. It should be one of " + StringUtils.join(preSignedObjTypes, ","))
+ if(StringUtils.isNotBlank(filePath) && filePath.size > 100)
+ throw new ClientException("ERR_CONTENT_INVALID_FILE_PATH", "Please provide valid filepath of character length 100 or Less ")
+ }
+
+ def dataModifier(node: Node): Node = {
+ if(node.getMetadata.containsKey("trackable") &&
+ node.getMetadata.getOrDefault("trackable", new java.util.HashMap[String, AnyRef]).asInstanceOf[java.util.Map[String, AnyRef]].containsKey("enabled") &&
+ "Yes".equalsIgnoreCase(node.getMetadata.getOrDefault("trackable", new java.util.HashMap[String, AnyRef]).asInstanceOf[java.util.Map[String, AnyRef]].getOrDefault("enabled", "").asInstanceOf[String])) {
+ node.getMetadata.put("contentType", "Course")
+ }
+ node
+ }
+
+ def getImportConfig(): ImportConfig = {
+ val requiredProps = Platform.getStringList("import.required_props", java.util.Arrays.asList("name", "code", "mimeType", "contentType", "artifactUrl", "framework")).asScala.toList
+ val validStages = Platform.getStringList("import.valid_stages", java.util.Arrays.asList("create", "upload", "review", "publish")).asScala.toList
+ val propsToRemove = Platform.getStringList("import.remove_props", java.util.Arrays.asList("downloadUrl", "variants", "previewUrl", "streamingUrl", "itemSets")).asScala.toList
+ val topicName = Platform.config.getString("import.output_topic_name")
+ val reqLimit = Platform.getInteger("import.request_size_limit", 200)
+ ImportConfig(topicName, reqLimit, requiredProps, validStages, propsToRemove)
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventActor.scala
new file mode 100644
index 000000000..cd42072d8
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventActor.scala
@@ -0,0 +1,82 @@
+package org.sunbird.content.actors
+
+import org.apache.commons.lang.StringUtils
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.content.util.ContentConstants
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.{Node, Relation}
+import org.sunbird.graph.nodes.DataNode
+
+import java.util
+import javax.inject.Inject
+import scala.collection.JavaConverters.asScalaBufferConverter
+import scala.concurrent.Future
+
+class EventActor @Inject()(implicit oec: OntologyEngineContext, ss: StorageService) extends ContentActor {
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case "createContent" => create(request)
+ case "readContent" => read(request)
+ case "updateContent" => update(request)
+ case "retireContent" => retire(request)
+ case "discardContent" => discard(request)
+ case "publishContent" => publish(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+ override def update(request: Request): Future[Response] = {
+ verifyStandaloneEventAndApply(super.update, request, Some(node => {
+ if (!"Draft".equalsIgnoreCase(node.getMetadata.getOrDefault("status", "").toString)) {
+ throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Update not allowed! Event status isn't draft")
+ }
+ }))
+ }
+
+ def publish(request: Request): Future[Response] = {
+ verifyStandaloneEventAndApply(super.update, request, Some(node => {
+ if (!"Draft".equalsIgnoreCase(node.getMetadata.getOrDefault("status", "").toString)) {
+ throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Publish not allowed! Event status isn't draft")
+ }
+ val versionKey = node.getMetadata.getOrDefault("versionKey", "").toString
+ if (StringUtils.isNotBlank(versionKey))
+ request.put("versionKey", versionKey)
+ }))
+ }
+
+ override def discard(request: Request): Future[Response] = {
+ verifyStandaloneEventAndApply(super.discard, request)
+ }
+
+ override def retire(request: Request): Future[Response] = {
+ verifyStandaloneEventAndApply(super.retire, request)
+ }
+
+ private def verifyStandaloneEventAndApply(f: Request => Future[Response], request: Request, dataUpdater: Option[Node => Unit] = None): Future[Response] = {
+ DataNode.read(request).flatMap(node => {
+ val inRelations = if (node.getInRelations == null) new util.ArrayList[Relation]() else node.getInRelations;
+ val hasEventSetParent = inRelations.asScala.exists(rel => "EventSet".equalsIgnoreCase(rel.getStartNodeObjectType))
+ if (hasEventSetParent)
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), "ERROR: Can't modify an Event which is part of an Event Set!"))
+ else {
+ if (dataUpdater.isDefined) {
+ dataUpdater.get.apply(node)
+ }
+ f.apply(request)
+ }
+ })
+ }
+
+ override def dataModifier(node: Node): Node = {
+ if (node.getMetadata.containsKey("trackable") &&
+ node.getMetadata.getOrDefault("trackable", new java.util.HashMap[String, AnyRef]).asInstanceOf[java.util.Map[String, AnyRef]].containsKey("enabled") &&
+ "Yes".equalsIgnoreCase(node.getMetadata.getOrDefault("trackable", new java.util.HashMap[String, AnyRef]).asInstanceOf[java.util.Map[String, AnyRef]].getOrDefault("enabled", "").asInstanceOf[String])) {
+ node.getMetadata.put("contentType", "Event")
+ }
+ node
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventSetActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventSetActor.scala
new file mode 100644
index 000000000..2894cc228
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/EventSetActor.scala
@@ -0,0 +1,255 @@
+package org.sunbird.content.actors
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.content.util.{ContentConstants, DiscardManager, RetireManager}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.common.enums.SystemProperties
+import org.sunbird.graph.dac.model.{Node, Relation}
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.util.RequestUtil
+import org.sunbird.utils.HierarchyConstants
+
+import java.util
+import javax.inject.Inject
+import scala.collection.JavaConverters._
+import scala.concurrent.Future
+
+class EventSetActor @Inject()(implicit oec: OntologyEngineContext, ss: StorageService) extends ContentActor {
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getContext.put(HierarchyConstants.SCHEMA_NAME, HierarchyConstants.EVENT_SET_SCHEMA_NAME)
+ request.getOperation match {
+ case "createContent" => create(request)
+ case "updateContent" => update(request)
+ case "publishContent" => publish(request)
+ case "getHierarchy" => getHierarchy(request)
+ case "readContent" => read(request)
+ case "retireContent" => retire(request)
+ case "discardContent" => discard(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+ override def create(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ val originalRequestContent = request.getRequest
+ addChildEvents(request)
+ .map(nodes => updateRequestWithChildRelations(request, originalRequestContent, nodes))
+ .flatMap(req => {
+ DataNode.create(req).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier)
+ .put("versionKey", node.getMetadata.get("versionKey"))
+ })
+ }).recoverWith {
+ case clientException: ClientException =>
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessage))
+ case e: Exception =>
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage))
+ }
+ }
+
+ override def update(request: Request): Future[Response] = {
+ if (StringUtils.isBlank(request.getRequest.getOrDefault("versionKey", "").asInstanceOf[String])) throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!")
+ RequestUtil.restrictProperties(request)
+ val originalRequestContent = request.getRequest
+ DataNode.read(request).flatMap(node => {
+ if (!"Draft".equalsIgnoreCase(node.getMetadata.getOrDefault("status", "").toString)) {
+ throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Update not allowed! EventSet status isn't draft")
+ }
+ deleteExistingEvents(node.getOutRelations, request).flatMap(_ => {
+ addChildEvents(request).map(nodes => {
+ updateRequestWithChildRelations(request, originalRequestContent, nodes)
+ }).flatMap(req =>
+ DataNode.update(req).map(node => {
+ val identifier: String = node.getIdentifier.replace(".img", "")
+ ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier)
+ .put("versionKey", node.getMetadata.get("versionKey"))
+ })
+ )
+ })
+ }).recoverWith {
+ case clientException: ClientException =>
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessage))
+ case e: Exception =>
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage))
+ }
+ }
+
+ def publish(request: Request): Future[Response] = {
+ DataNode.read(request).flatMap(node => {
+ if (!"Draft".equalsIgnoreCase(node.getMetadata.getOrDefault("status", "").toString)) {
+ throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Publish not allowed! EventSet status isn't draft")
+ }
+ publishChildEvents(node.getOutRelations, request).flatMap(_ => {
+ val existingVersionKey = node.getMetadata.getOrDefault("versionKey", "").asInstanceOf[String]
+ request.put("versionKey", existingVersionKey)
+ request.getContext.put("identifier", node.getIdentifier)
+ request.put("status", "Live")
+ DataNode.update(request).map(updateNode => {
+ val identifier: String = updateNode.getIdentifier.replace(".img", "")
+ ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier)
+ .put("versionKey", updateNode.getMetadata.get("versionKey"))
+ })
+ }
+ )
+ }).recoverWith {
+ case clientException: ClientException =>
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessage))
+ case e: Exception =>
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage))
+ }
+ }
+
+ override def retire(request: Request): Future[Response] = {
+ DataNode.read(request).flatMap(node => {
+ retireChildEvents(node.getOutRelations, request)
+ .flatMap(_ => RetireManager.retire(request))
+ }).recoverWith {
+ case clientException: ClientException =>
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessage))
+ case e: Exception =>
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage))
+ }
+ }
+
+ override def discard(request: Request): Future[Response] = {
+ DataNode.read(request).flatMap(node => discardChildEvents(node.getOutRelations, request)
+ .flatMap(_ => DiscardManager.discard(request)))
+ .recoverWith {
+ case clientException: ClientException =>
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessage))
+ case e: Exception =>
+ Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage))
+ }
+ }
+
+ def getHierarchy(request: Request): Future[Response] = {
+ val fields: util.List[String] = seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ val outRelations = if (node.getOutRelations == null) List[Relation]() else node.getOutRelations.asScala
+ val childNodes = outRelations.map(relation => relation.getEndNodeId).toList
+ val children = outRelations.map(relation => relation.getEndNodeMetadata).map(metadata => {
+ SystemProperties.values().foreach(value => metadata.remove(value.name()))
+ metadata
+ }).toList
+
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, new util.ArrayList[String](), node.getObjectType.toLowerCase.replace("image", ""), request.getContext.get("version").asInstanceOf[String], true)
+ metadata.put("identifier", node.getIdentifier.replace(".img", ""))
+ metadata.put("childNodes", childNodes.asJava)
+ metadata.put("children", children.asJava)
+ ResponseHandler.OK.put("eventset", metadata)
+ })
+ }
+
+ private def updateRequestWithChildRelations(request: Request, originalRequestContent: util.Map[String, AnyRef], nodes: List[Node]) = {
+ request.setRequest(originalRequestContent)
+ val relations = new util.ArrayList[util.Map[String, String]]()
+ nodes.foreach(node => {
+ relations.add(Map("identifier" -> node.getIdentifier).asJava)
+ })
+ request.getRequest.put("collections", relations)
+ request
+ }
+
+ private def addChildEvents(request: Request) = {
+ val newChildEvents = formChildEvents(request)
+ Future.sequence(newChildEvents.map(childEvent => {
+ val childRequest = new Request(request)
+ childRequest.setRequest(childEvent.asJava)
+ childRequest.getContext.put("schemaName", "event")
+ childRequest.getContext.put("objectType", "Event")
+ DataNode.create(childRequest)
+ }))
+ }
+
+ private def formChildEvents(contentRequest: Request): List[collection.mutable.Map[String, AnyRef]] = {
+ val scheduleObject = contentRequest.getRequest.getOrDefault("schedule", new util.HashMap[String, Object]()).asInstanceOf[util.Map[String, Object]]
+ val schedules = scheduleObject.getOrDefault("value", new util.ArrayList[util.Map[String, String]]()).asInstanceOf[util.List[util.Map[String, String]]].asScala
+ schedules.map(schedule => {
+ var event = collection.mutable.Map[String, AnyRef]() ++ contentRequest.getRequest.asScala
+ event ++= schedule.asScala
+ event -= "schedule"
+ event -= "identifier"
+ event
+ }).toList
+ }
+
+ private def deleteExistingEvents(relations: util.List[Relation], request: Request) = {
+ if (relations != null)
+ Future.sequence(relations.asScala.filter(rel => "Event".equalsIgnoreCase(rel.getEndNodeObjectType)).map(relation => {
+ val deleteReq = new Request()
+ deleteReq.setContext(request.getContext)
+ val delMap = new util.HashMap[String, AnyRef]()
+ delMap.put("identifier", relation.getEndNodeId)
+ deleteReq.setRequest(delMap)
+ DataNode.deleteNode(deleteReq)
+ }))
+ else
+ Future(List())
+ }
+
+ private def publishChildEvents(relations: util.List[Relation], request: Request) = {
+ if (relations != null)
+ Future.sequence(relations.asScala.filter(rel => "Event".equalsIgnoreCase(rel.getEndNodeObjectType)).map(relation => {
+ val updateReq = new Request()
+ val context = new util.HashMap[String, Object]()
+ context.putAll(request.getContext)
+ updateReq.setContext(context)
+ updateReq.getContext.put("schemaName", "event")
+ updateReq.getContext.put("objectType", "Event")
+ updateReq.getContext.put("identifier", relation.getEndNodeId)
+ val updateMap = new util.HashMap[String, AnyRef]()
+ updateMap.put("identifier", relation.getEndNodeId)
+ updateMap.put("status", "Live")
+ updateReq.setRequest(updateMap)
+ DataNode.update(updateReq)
+ }))
+ else
+ Future(List())
+ }
+
+ private def retireChildEvents(relations: util.List[Relation], request: Request) = {
+ if (relations != null)
+ Future.sequence(relations.asScala.filter(rel => "Event".equalsIgnoreCase(rel.getEndNodeObjectType)).map(relation => {
+ val retireReq = new Request()
+ val context = new util.HashMap[String, Object]()
+ context.putAll(request.getContext)
+ retireReq.setContext(context)
+ retireReq.getContext.put("schemaName", "event")
+ retireReq.getContext.put("objectType", "Event")
+ retireReq.getContext.put("identifier", relation.getEndNodeId)
+ val updateMap = new util.HashMap[String, AnyRef]()
+ updateMap.put("identifier", relation.getEndNodeId)
+ retireReq.setRequest(updateMap)
+ RetireManager.retire(retireReq)
+
+ }))
+ else
+ Future(List())
+ }
+
+ private def discardChildEvents(relations: util.List[Relation], request: Request) = {
+ if (relations != null)
+ Future.sequence(relations.asScala.filter(rel => "Event".equalsIgnoreCase(rel.getEndNodeObjectType)).map(relation => {
+ val discardReq = new Request()
+ val context = new util.HashMap[String, Object]()
+ context.putAll(request.getContext)
+ discardReq.setContext(context)
+ discardReq.getContext.put("schemaName", "event")
+ discardReq.getContext.put("objectType", "Event")
+ discardReq.getContext.put("identifier", relation.getEndNodeId)
+ val updateMap = new util.HashMap[String, AnyRef]()
+ updateMap.put("identifier", relation.getEndNodeId)
+ discardReq.setRequest(updateMap)
+ RetireManager.retire(discardReq)
+
+ }))
+ else
+ Future(List())
+ }
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/HealthActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/HealthActor.scala
new file mode 100644
index 000000000..3e98eaa5f
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/HealthActor.scala
@@ -0,0 +1,17 @@
+package org.sunbird.content.actors
+
+import javax.inject.Inject
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.health.HealthCheckManager
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class HealthActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ HealthCheckManager.checkAllSystemHealth()
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/LicenseActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/LicenseActor.scala
new file mode 100644
index 000000000..b637c6704
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/LicenseActor.scala
@@ -0,0 +1,72 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import javax.inject.Inject
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.Slug
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.content.util.LicenseConstants
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.util.RequestUtil
+
+import scala.collection.JavaConverters
+import scala.concurrent.{ExecutionContext, Future}
+
+class LicenseActor @Inject() (implicit oec: OntologyEngineContext) extends BaseActor {
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ request.getOperation match {
+ case LicenseConstants.CREATE_LICENSE => create(request)
+ case LicenseConstants.READ_LICENSE => read(request)
+ case LicenseConstants.UPDATE_LICENSE => update(request)
+ case LicenseConstants.RETIRE_LICENSE => retire(request)
+ case _ => ERROR(request.getOperation)
+ }
+ }
+
+
+ @throws[Exception]
+ private def create(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ if (request.getRequest.containsKey("identifier")) throw new ClientException("ERR_NAME_SET_AS_IDENTIFIER", "name will be set as identifier")
+ if (request.getRequest.containsKey("name")) request.getRequest.put("identifier", Slug.makeSlug(request.getRequest.get("name").asInstanceOf[String]))
+ DataNode.create(request).map(node => {
+ ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier)
+ })
+ }
+
+ @throws[Exception]
+ private def read(request: Request): Future[Response] = {
+ val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava
+ request.getRequest.put("fields", fields)
+ DataNode.read(request).map(node => {
+ if (NodeUtil.isRetired(node)) ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name, "License not found with identifier: " + node.getIdentifier)
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ ResponseHandler.OK.put("license", metadata)
+ })
+ }
+
+ @throws[Exception]
+ private def update(request: Request): Future[Response] = {
+ RequestUtil.restrictProperties(request)
+ request.getRequest.put("status", "Live")
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("node_id", node.getIdentifier).put("identifier", node.getIdentifier)
+ })
+ }
+
+ @throws[Exception]
+ private def retire(request: Request): Future[Response] = {
+ request.getRequest.put("status", "Retired")
+ DataNode.update(request).map(node => {
+ ResponseHandler.OK.put("node_id", node.getIdentifier).put("identifier", node.getIdentifier)
+ })
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALConstants.scala
new file mode 100644
index 000000000..be127079b
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALConstants.scala
@@ -0,0 +1,20 @@
+package org.sunbird.content.dial
+
+object DIALConstants {
+
+ val CONTENT: String = "content"
+ val COLLECTION: String = "collection"
+ val LINK_TYPE: String = "linkType"
+ val CHANNEL: String = "channel"
+ val IDENTIFIER: String = "identifier"
+ val IDENTIFIERS: String = "identifiers"
+ val DIALCODE: String = "dialcode"
+ val DIALCODES: String = "dialcodes"
+ val COUNT: String = "count"
+ val REQUEST: String = "request"
+ val SEARCH: String = "search"
+ val AUTHORIZATION: String = "Authorization"
+ val X_CHANNEL_ID: String = "X-Channel-Id"
+ val VERSION_KEY: String = "versionKey"
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALErrors.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALErrors.scala
new file mode 100644
index 000000000..8d70cfc7c
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALErrors.scala
@@ -0,0 +1,16 @@
+package org.sunbird.content.dial
+
+object DIALErrors {
+
+ //Error Codes
+ val ERR_DIALCODE_LINK_REQUEST: String = "ERR_DIALCODE_LINK_REQUEST"
+ val ERR_DIALCODE_LINK: String = "ERR_DIALCODE_LINK"
+
+ //Error Messages
+ val ERR_INVALID_REQ_MSG: String = "Invalid Request! Please Provide Valid Request."
+ val ERR_REQUIRED_PROPS_MSG: String = "Invalid Request! Please Provide Required Properties In Request."
+ val ERR_MAX_LIMIT_MSG: String = "Max Limit For Link Content To DIAL Code In A Request Is "
+ val ERR_DIAL_NOT_FOUND_MSG: String = "DIAL Code Not Found With Id(s): "
+ val ERR_CONTENT_NOT_FOUND_MSG: String = "Content Not Found With Id(s): "
+ val ERR_SERVER_ERROR_MSG: String = "Something Went Wrong While Processing Your Request. Please Try Again After Sometime!"
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALManager.scala
new file mode 100644
index 000000000..487ac475c
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/dial/DIALManager.scala
@@ -0,0 +1,152 @@
+package org.sunbird.content.dial
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.Platform
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import java.util
+import scala.collection.immutable.HashMap
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+
+object DIALManager {
+
+ val DIAL_SEARCH_API_URL = Platform.config.getString("dial_service.api.base_url") + "/dialcode/v3/search"
+ val DIAL_API_AUTH_KEY = "Bearer " + Platform.config.getString("dial_service.api.auth_key")
+ val PASSPORT_KEY = Platform.config.getString("graph.passport.key.base")
+
+ def link(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val linkType: String = request.getContext.getOrDefault(DIALConstants.LINK_TYPE, DIALConstants.CONTENT).asInstanceOf[String]
+ val channelId: String = request.getContext.getOrDefault(DIALConstants.CHANNEL, "").asInstanceOf[String]
+ val objectId: String = request.getContext.getOrDefault(DIALConstants.IDENTIFIER, "").asInstanceOf[String]
+ val reqList: List[Map[String, List[String]]] = getRequestData(request)
+ val requestMap: Map[String, List[String]] = validateAndGetRequestMap(channelId, reqList)
+ linkType match {
+ case DIALConstants.CONTENT => linkContent(requestMap, request.getContext)
+ case DIALConstants.COLLECTION => linkCollection(objectId, requestMap, request.getContext)
+ case _ => throw new ClientException(DIALErrors.ERR_DIALCODE_LINK_REQUEST, DIALErrors.ERR_INVALID_REQ_MSG)
+ }
+ }
+
+ def getRequestData(request: Request): List[Map[String, List[String]]] = {
+ val req = request.getRequest.get(DIALConstants.CONTENT)
+ req match {
+ case req: util.List[util.Map[String, AnyRef]] => req.asScala.toList.map(obj => obj.asScala.toMap.map(x => (x._1, getList(x._2))))
+ case req: util.Map[String, AnyRef] => List(req.asScala.toMap.map(x => (x._1, getList(x._2))))
+ case _ => throw new ClientException(DIALErrors.ERR_DIALCODE_LINK_REQUEST, DIALErrors.ERR_INVALID_REQ_MSG)
+ }
+ }
+
+ def getList(obj: AnyRef): List[String] = {
+ (obj match {
+ case obj: util.List[String] => obj.asScala.toList.distinct
+ case obj: String => List(obj).distinct
+ case _ => List.empty
+ }).filter((x: String) => StringUtils.isNotBlank(x) && !StringUtils.equals(" ", x))
+ }
+
+ def validateAndGetRequestMap(channelId: String, requestList: List[Map[String, List[String]]])(implicit oec:OntologyEngineContext): Map[String, List[String]] = {
+ var reqMap = HashMap[String, List[String]]()
+ requestList.foreach(req => {
+ val contents: List[String] = req.get(DIALConstants.IDENTIFIER).get
+ val dialcodes: List[String] = req.get(DIALConstants.DIALCODE).get
+ validateReqStructure(dialcodes, contents)
+ contents.foreach(id => reqMap += (id -> dialcodes))
+ })
+ if (Platform.getBoolean("content.link_dialcode.validation", true)) {
+ val dials = requestList.collect { case m if m.get(DIALConstants.DIALCODE).nonEmpty => m.get(DIALConstants.DIALCODE).get }.flatten
+ validateDialCodes(channelId, dials)
+ }
+ reqMap
+ }
+
+ def validateReqStructure(dialcodes: List[String], contents: List[String]): Unit = {
+ if (null == dialcodes || null == contents || contents.isEmpty)
+ throw new ClientException(DIALErrors.ERR_DIALCODE_LINK_REQUEST, DIALErrors.ERR_REQUIRED_PROPS_MSG)
+ val maxLimit: Int = Platform.getInteger("content.link_dialcode.max_limit", 10)
+ if (dialcodes.size >= maxLimit || contents.size >= maxLimit)
+ throw new ClientException(DIALErrors.ERR_DIALCODE_LINK_REQUEST, DIALErrors.ERR_MAX_LIMIT_MSG + maxLimit)
+ }
+
+ def validateDialCodes(channelId: String, dialcodes: List[String])(implicit oec: OntologyEngineContext): Boolean = {
+ if (!dialcodes.isEmpty) {
+ val reqMap = new util.HashMap[String, AnyRef]() {{
+ put(DIALConstants.REQUEST, new util.HashMap[String, AnyRef]() {{
+ put(DIALConstants.SEARCH, new util.HashMap[String, AnyRef]() {{
+ put(DIALConstants.IDENTIFIER, dialcodes.distinct.asJava)
+ }})
+ }})
+ }}
+ val headerParam = HashMap[String, String](DIALConstants.X_CHANNEL_ID -> channelId, DIALConstants.AUTHORIZATION -> DIAL_API_AUTH_KEY).asJava
+ val searchResponse = oec.httpUtil.post(DIAL_SEARCH_API_URL, reqMap, headerParam)
+ if (searchResponse.getResponseCode.toString == "OK") {
+ val result = searchResponse.getResult
+ if (dialcodes.distinct.size == result.get(DIALConstants.COUNT).asInstanceOf[Integer]) {
+ return true
+ } else {
+ val dials = result.get(DIALConstants.DIALCODES).asInstanceOf[util.List[util.Map[String, AnyRef]]].asScala.toList.map(obj => obj.asScala.toMap).map(_.getOrElse(DIALConstants.IDENTIFIER, "")).asInstanceOf[List[String]]
+ throw new ResourceNotFoundException(DIALErrors.ERR_DIALCODE_LINK, DIALErrors.ERR_DIAL_NOT_FOUND_MSG + dialcodes.distinct.diff(dials).asJava)
+ }
+ }
+ else throw new ServerException(ErrorCodes.ERR_SYSTEM_EXCEPTION.name, DIALErrors.ERR_SERVER_ERROR_MSG)
+ }
+ true
+ }
+
+ def linkContent(requestMap: Map[String, List[String]], reqContext: util.Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateContents(requestMap, reqContext).map(result => {
+ val futureList: List[Future[Node]] = requestMap.filter(x => !result.contains(x._1)).map(map => {
+ val updateReqMap = new util.HashMap[String, AnyRef]() {{
+ val dials: util.List[String] = if (!map._2.isEmpty) map._2.asJava else new util.ArrayList[String]()
+ put(DIALConstants.DIALCODES, dials)
+ put(DIALConstants.VERSION_KEY, PASSPORT_KEY)
+ }}
+ val updateRequest = new Request()
+ reqContext.put(DIALConstants.IDENTIFIER, map._1)
+ updateRequest.setContext(reqContext)
+ updateRequest.putAll(updateReqMap)
+ DataNode.update(updateRequest)
+ }).toList
+ val updatedNodes: Future[List[Node]] = Future.sequence(futureList)
+ getResponse(requestMap, updatedNodes, result)
+ }).flatMap(f => f)
+ }
+
+ //TODO: Complete the implementation
+ def linkCollection(objectId: String, requestMap: Map[String, List[String]], getContext: util.Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ Future {
+ ResponseHandler.OK()
+ }
+ }
+
+ def validateContents(requestMap: Map[String, List[String]], reqContext: util.Map[String, AnyRef])(implicit ec: ExecutionContext, oec:OntologyEngineContext): Future[List[String]] = {
+ val request = new Request()
+ request.setContext(reqContext)
+ request.put(DIALConstants.IDENTIFIERS, requestMap.keys.toList.asJava)
+ DataNode.list(request).map(obj => {
+ if (null != obj && !obj.isEmpty) {
+ val identifiers = obj.asScala.collect { case node if null != node => node.getIdentifier }.toList
+ Future {
+ requestMap.keys.toList.diff(identifiers)
+ }
+ } else throw new ResourceNotFoundException(DIALErrors.ERR_DIALCODE_LINK, DIALErrors.ERR_CONTENT_NOT_FOUND_MSG + requestMap.keySet.asJava)
+ }).flatMap(f => f)
+ }
+
+ def getResponse(requestMap: Map[String, List[String]], updatedNodes: Future[List[Node]], invalidIds: List[String])(implicit ec: ExecutionContext): Future[Response] = {
+ updatedNodes.map(obj => {
+ val successIds = obj.collect { case node if null != node => node.getIdentifier }
+ if (requestMap.keySet.size == successIds.size)
+ ResponseHandler.OK
+ else if (invalidIds.nonEmpty && successIds.isEmpty)
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, DIALErrors.ERR_DIALCODE_LINK, DIALErrors.ERR_CONTENT_NOT_FOUND_MSG + invalidIds.asJava)
+ else
+ ResponseHandler.ERROR(ResponseCode.PARTIAL_SUCCESS, DIALErrors.ERR_DIALCODE_LINK, DIALErrors.ERR_CONTENT_NOT_FOUND_MSG + invalidIds.asJava)
+ })
+ }
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/UploadManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/UploadManager.scala
new file mode 100644
index 000000000..e6a26dbe3
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/UploadManager.scala
@@ -0,0 +1,115 @@
+package org.sunbird.content.upload.mgr
+
+import java.io.File
+import java.util
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.models.UploadParams
+import org.sunbird.common.Platform
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.content.util.ContentConstants
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.mimetype.factory.MimeTypeManagerFactory
+import org.sunbird.telemetry.util.LogTelemetryEventUtil
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.{ExecutionContext, Future}
+import org.sunbird.kafka.client.KafkaClient
+
+import scala.collection.Map
+
+object UploadManager {
+
+ private val MEDIA_TYPE_LIST = List("image", "video")
+ private val kfClient = new KafkaClient
+ private val CONTENT_ARTIFACT_ONLINE_SIZE: Double = Platform.getDouble("content.artifact.size.for_online", 209715200.asInstanceOf[Double])
+
+
+ def upload(request: Request, node: Node)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val identifier: String = node.getIdentifier
+ val fileUrl: String = request.getRequest.getOrDefault("fileUrl", "").asInstanceOf[String]
+ val file = request.getRequest.get("file").asInstanceOf[File]
+ val reqFilePath: String = request.getRequest.getOrDefault("filePath", "").asInstanceOf[String].replaceAll("^/+|/+$", "")
+ val filePath = if(StringUtils.isBlank(reqFilePath)) None else Option(reqFilePath)
+ val mimeType = node.getMetadata().getOrDefault("mimeType", "").asInstanceOf[String]
+ val mediaType = node.getMetadata.getOrDefault("mediaType", "").asInstanceOf[String]
+ val mgr = MimeTypeManagerFactory.getManager(node.getObjectType, mimeType)
+ val params: UploadParams = request.getContext.get("params").asInstanceOf[UploadParams]
+ val uploadFuture: Future[Map[String, AnyRef]] = if (StringUtils.isNotBlank(fileUrl)) mgr.upload(identifier, node, fileUrl, filePath, params) else mgr.upload(identifier, node, file, filePath, params)
+ uploadFuture.map(result => {
+ if(filePath.isDefined)
+ updateNode(request, node.getIdentifier, mediaType, node.getObjectType, result + (ContentConstants.ARTIFACT_BASE_PATH -> filePath.get))
+ else
+ updateNode(request, node.getIdentifier, mediaType, node.getObjectType, result)
+ }).flatMap(f => f)
+ }
+
+ def updateNode(request: Request, identifier: String, mediaType: String, objectType: String, result: Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val updatedResult = result - "identifier"
+ val artifactUrl = updatedResult.getOrElse("artifactUrl", "").asInstanceOf[String]
+ val size: Double = updatedResult.getOrElse("size", 0.asInstanceOf[Double]).asInstanceOf[Double]
+ if (StringUtils.isNotBlank(artifactUrl)) {
+ val updateReq = new Request(request)
+ updateReq.getContext().put("identifier", identifier)
+ updateReq.getRequest.putAll(mapAsJavaMap(updatedResult))
+ if( size > CONTENT_ARTIFACT_ONLINE_SIZE)
+ updateReq.put("contentDisposition", "online-only")
+ if (StringUtils.equalsIgnoreCase("Asset", objectType) && MEDIA_TYPE_LIST.contains(mediaType))
+ updateReq.put("status", "Processing")
+
+ DataNode.update(updateReq).map(node => {
+ if (StringUtils.equalsIgnoreCase("Asset", objectType) && MEDIA_TYPE_LIST.contains(mediaType) && null != node)
+ pushInstructionEvent(identifier, node)
+ getUploadResponse(node)
+ })
+ } else {
+ Future {
+ ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, "ERR_UPLOAD_FILE", "Something Went Wrong While Processing Your Request.")
+ }
+ }
+ }
+
+ def getUploadResponse(node: Node)(implicit ec: ExecutionContext): Response = {
+ val id = node.getIdentifier.replace(".img", "")
+ val url = node.getMetadata.get("artifactUrl").asInstanceOf[String]
+ ResponseHandler.OK.put("node_id", id).put("identifier", id).put("artifactUrl", url)
+ .put("content_url", url).put("versionKey", node.getMetadata.get("versionKey"))
+ }
+
+ @throws[Exception]
+ private def pushInstructionEvent(identifier: String, node: Node): Unit = {
+ val actor: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
+ val context: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
+ val objectData: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
+ val edata: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
+ generateInstructionEventMetadata(actor, context, objectData, edata, node, identifier)
+ val beJobRequestEvent: String = LogTelemetryEventUtil.logInstructionEvent(actor, context, objectData, edata)
+ val topic: String = Platform.getString("kafka.topics.instruction","sunbirddev.learning.job.request")
+ if (StringUtils.isBlank(beJobRequestEvent)) throw new ClientException("BE_JOB_REQUEST_EXCEPTION", "Event is not generated properly.")
+ kfClient.send(beJobRequestEvent, topic)
+ }
+
+ private def generateInstructionEventMetadata(actor: util.Map[String, AnyRef], context: util.Map[String, AnyRef], objectData: util.Map[String, AnyRef], edata: util.Map[String, AnyRef], node: Node, identifier: String): Unit = {
+ val metadata: util.Map[String, AnyRef] = node.getMetadata
+ actor.put("id", "Asset Enrichment Samza Job")
+ actor.put("type", "System")
+ context.put("channel", metadata.get("channel"))
+ context.put("pdata", new util.HashMap[String, AnyRef]() {{
+ put("id", "org.sunbird.platform")
+ put("ver", "1.0")
+ }})
+ if (Platform.config.hasPath("cloud_storage.env")) {
+ val env: String = Platform.getString("cloud_storage.env", "dev")
+ context.put("env", env)
+ }
+ objectData.put("id", identifier)
+ objectData.put("ver", metadata.get("versionKey"))
+ edata.put("action", "assetenrichment")
+ edata.put("status", metadata.get("status"))
+ edata.put("mediaType", metadata.get("mediaType"))
+ edata.put("objectType", node.getObjectType)
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/AcceptFlagManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AcceptFlagManager.scala
new file mode 100644
index 000000000..1fb570d7c
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AcceptFlagManager.scala
@@ -0,0 +1,93 @@
+package org.sunbird.content.util
+
+import java.util
+
+import org.apache.commons.lang.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.{DateUtils, Platform}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.service.common.DACConfigurationConstants
+import org.sunbird.graph.utils.ScalaJsonUtils
+import org.sunbird.managers.HierarchyManager
+
+import scala.concurrent.{ExecutionContext, Future}
+
+object AcceptFlagManager {
+
+ def acceptFlag(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ DataNode.read(request).map(node => {
+ if (!StringUtils.equals(ContentConstants.FLAGGED, node.getMetadata.getOrDefault(ContentConstants.STATUS, "").asInstanceOf[String])) {
+ Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ContentConstants.ERR_INVALID_CONTENT, "Invalid Flagged Content! Content Can Not Be Accepted."))
+ } else {
+ request.getContext.put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ createOrUpdateImageNode(request, node).map(imgNode => {
+ updateOriginalNode(request, node).map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ response.put(ContentConstants.NODE_ID, node.getIdentifier)
+ response.put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ response.put(ContentConstants.VERSION_KEY, imgNode.getMetadata.get(ContentConstants.VERSION_KEY))
+ response
+ } else {
+ response
+ }
+ })
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+
+ private def createOrUpdateImageNode(request: Request, node: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val req: Request = new Request(request)
+ req.put(ContentConstants.STATUS, ContentConstants.FLAG_DRAFT)
+ req.put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ DataNode.update(req).map(node => {
+ node
+ })
+ }
+
+ private def updateOriginalNode(request: Request, node: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ val currentDate = DateUtils.formatCurrentDate
+ request.put(ContentConstants.STATUS, ContentConstants.RETIRED)
+ request.put(ContentConstants.LAST_STATUS_CHANGED_ON, currentDate)
+ request.put(ContentConstants.LAST_UPDATED_ON, currentDate)
+ request.put(ContentConstants.VERSION_KEY, Platform.config.getString(DACConfigurationConstants.PASSPORT_KEY_BASE_PROPERTY))
+
+ request.getContext.put(ContentConstants.VERSIONING, ContentConstants.DISABLE)
+
+ if (StringUtils.equals(node.getMetadata.getOrDefault(ContentConstants.MIME_TYPE, "").asInstanceOf[String], ContentConstants.COLLECTION_MIME_TYPE)) {
+ request.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ request.put(ContentConstants.ROOT_ID, request.get(ContentConstants.IDENTIFIER))
+ HierarchyManager.getHierarchy(request).map(hierarchyResponse => {
+ if (!ResponseHandler.checkError(hierarchyResponse)) {
+ val updatedHierarchy = hierarchyResponse.get(ContentConstants.CONTENT).asInstanceOf[util.Map[String, AnyRef]]
+ updatedHierarchy.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put(ContentConstants.STATUS, ContentConstants.RETIRED)
+ put(ContentConstants.LAST_STATUS_CHANGED_ON, currentDate)
+ put(ContentConstants.LAST_UPDATED_ON, currentDate)
+ }
+ })
+ request.put(ContentConstants.HIERARCHY, ScalaJsonUtils.serialize(updatedHierarchy))
+ RedisCache.delete(ContentConstants.HIERARCHY_PREFIX + request.get(ContentConstants.IDENTIFIER).asInstanceOf[String])
+ updateNode(request)
+ } else {
+ Future(hierarchyResponse)
+ }
+ }).flatMap(f => f)
+ } else {
+ updateNode(request)
+ }
+ }
+
+ private def updateNode(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ DataNode.update(request).map(updatedOriginalNode => {
+ RedisCache.delete(request.get(ContentConstants.IDENTIFIER).asInstanceOf[String])
+ ResponseHandler.OK()
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/ActorNames.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/ActorNames.scala
new file mode 100644
index 000000000..2c1ef8cd4
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/ActorNames.scala
@@ -0,0 +1,7 @@
+package org.sunbird.content.util
+
+object ActorNames {
+
+ final val COLLECTION_ACTOR = "collectionActor"
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetConstants.scala
new file mode 100644
index 000000000..0b815d1df
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetConstants.scala
@@ -0,0 +1,19 @@
+package org.sunbird.content.util
+
+object AssetConstants {
+ val ASSET_COPY_SCHEME: String = "assetCopyScheme"
+ val STATUS: String = "status"
+ val ORIGIN: String = "origin"
+ val IDENTIFIER: String = "identifier"
+ val ORIGIN_DATA: String = "originData"
+ val SCHEMA_NAME: String = "schemaName"
+ val OBJECT_TYPE: String = "objectType"
+ val VERSION_KEY: String = "versionKey"
+ val ARTIFACT_URL: String = "artifactUrl"
+ val MIME_TYPE: String = "mimeType"
+ val NAME: String = "name"
+ val SCHEMA_VERSION: String = "1.0"
+ val ERR_INVALID_REQUEST: String = "ERR_INVALID_REQUEST"
+ val MEDIA_TYPE: String = "mediaType"
+ val FILE_URL: String = "fileUrl"
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetCopyManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetCopyManager.scala
new file mode 100644
index 000000000..b45e85dc3
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/AssetCopyManager.scala
@@ -0,0 +1,89 @@
+package org.sunbird.content.util
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import org.apache.commons.lang.StringUtils
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ClientException
+import org.sunbird.content.upload.mgr.UploadManager
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.common.Identifier
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+
+import scala.concurrent.{ExecutionContext, Future}
+
+object AssetCopyManager {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ implicit val ss: StorageService = new StorageService
+
+ def copy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ request.getContext.put(AssetConstants.ASSET_COPY_SCHEME, request.getRequest.getOrDefault(AssetConstants.ASSET_COPY_SCHEME, ""))
+ DataNode.read(request).map(node => {
+ if (!StringUtils.equals(node.getObjectType, "Asset"))
+ throw new ClientException(AssetConstants.ERR_INVALID_REQUEST, "Only asset can be copied")
+ val copiedNodeFuture: Future[Node] = copyAsset(node, request)
+ copiedNodeFuture.map(copiedNode => {
+ val response = ResponseHandler.OK()
+ response.put("node_id", new util.HashMap[String, AnyRef]() {
+ {
+ put(node.getIdentifier, copiedNode.getIdentifier)
+ }
+ })
+ response.put(AssetConstants.VERSION_KEY, copiedNode.getMetadata.get(AssetConstants.VERSION_KEY))
+ response
+ })
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def copyAsset(node: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val copyCreateReq: Future[Request] = getCopyRequest(node, request)
+ copyCreateReq.map(req => {
+ DataNode.create(req).map(copiedNode => {
+ artifactUpload(node, copiedNode, request)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+
+ def getCopyRequest(node: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Request] = {
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, new util.ArrayList(), node.getObjectType.toLowerCase.replace("image", ""), AssetConstants.SCHEMA_VERSION)
+ val requestMap = request.getRequest
+ requestMap.remove(AssetConstants.ASSET_COPY_SCHEME).asInstanceOf[String]
+ metadata.put(AssetConstants.ORIGIN, node.getIdentifier)
+ metadata.put(AssetConstants.NAME, "Copy_" + metadata.getOrDefault(AssetConstants.NAME, ""))
+ metadata.put(AssetConstants.STATUS, "Draft")
+ metadata.put(AssetConstants.IDENTIFIER, Identifier.getIdentifier(request.getContext.get("graph_id").asInstanceOf[String], Identifier.getUniqueIdFromTimestamp))
+ metadata.putAll(requestMap.getOrDefault("asset", new util.HashMap()).asInstanceOf[util.Map[String, AnyRef]])
+ request.getContext().put(AssetConstants.SCHEMA_NAME, node.getObjectType.toLowerCase.replace("image", ""))
+ val req = new Request(request)
+ req.setRequest(metadata)
+ Future {
+ req
+ }
+ }
+
+ def artifactUpload(node: Node, copiedNode: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val artifactUrl = node.getMetadata.getOrDefault(AssetConstants.ARTIFACT_URL, "").asInstanceOf[String]
+ if (StringUtils.isNotBlank(artifactUrl)) {
+ val updatedReq = getUpdateRequest(request, copiedNode)
+ val responseFuture = UploadManager.upload(updatedReq, copiedNode)
+ responseFuture.map(result => {
+ copiedNode.getMetadata.put(AssetConstants.ARTIFACT_URL, result.getResult.getOrDefault(AssetConstants.ARTIFACT_URL, "").asInstanceOf[String])
+ })
+ }
+ Future(copiedNode)
+ }
+
+ def getUpdateRequest(request: Request, copiedNode: Node): Request = {
+ val req = new Request()
+ val context = request.getContext
+ context.put(AssetConstants.IDENTIFIER, copiedNode.getIdentifier)
+ req.setContext(context)
+ req.put(AssetConstants.VERSION_KEY, copiedNode.getMetadata.get(AssetConstants.VERSION_KEY))
+ req.put(AssetConstants.FILE_URL, copiedNode.getMetadata.get(AssetConstants.ARTIFACT_URL))
+ req
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/CategoryConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/CategoryConstants.scala
new file mode 100644
index 000000000..9b3fa30c6
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/CategoryConstants.scala
@@ -0,0 +1,8 @@
+package org.sunbird.content.util
+
+object CategoryConstants {
+ val CREATE_CATEGORY: String = "createCategory"
+ val READ_CATEGORY: String = "readCategory"
+ val UPDATE_CATEGORY: String = "updateCategory"
+ val RETIRE_CATEGORY: String = "retireCategory"
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/ContentConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/ContentConstants.scala
new file mode 100644
index 000000000..29889d0eb
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/ContentConstants.scala
@@ -0,0 +1,59 @@
+package org.sunbird.content.util
+
+object ContentConstants {
+ val CHILDREN: String = "children"
+ val REQUIRED_KEYS: List[String] = List("createdBy", "createdFor", "organisation", "framework")
+ val CONTENT_TYPE: String = "contentType"
+ val CONTENT_TYPE_ASSET_CAN_NOT_COPY: String = "CONTENT_TYPE_ASSET_CAN_NOT_COPY"
+ val STATUS: String = "status"
+ val ERR_INVALID_REQUEST: String = "ERR_INVALID_REQUEST"
+ val MIME_TYPE: String = "mimeType"
+ val CONTENT_SCHEMA_NAME: String = "content"
+ val SCHEMA_NAME: String = "schemaName"
+ val RESPONSE_SCHEMA_NAME: String = "responseSchemaName"
+ val SCHEMA_VERSION: String = "1.0"
+ val ARTIFACT_URL: String = "artifactUrl"
+ val IDENTIFIER: String = "identifier"
+ val MODE: String = "mode"
+ val COLLECTION_MIME_TYPE: String = "application/vnd.ekstep.content-collection"
+ val ORIGIN: String = "origin"
+ val ORIGIN_DATA: String = "originData"
+ val ERR_INVALID_UPLOAD_FILE_URL: String = "ERR_INVALID_UPLOAD_FILE_URL"
+ val ROOT_ID: String = "rootId"
+ val HIERARCHY: String = "hierarchy"
+ val ROOT: String = "root"
+ val NODES_MODIFIED: String = "nodesModified"
+ val VISIBILITY: String = "visibility"
+ val METADATA: String = "metadata"
+ val END_NODE_OBJECT_TYPES = List("Content", "ContentImage")
+ val VERSION_KEY: String = "versionKey"
+ val H5P_MIME_TYPE = "application/vnd.ekstep.h5p-archive"
+ val COPY_TYPE: String = "copyType"
+ val COPY_TYPE_SHALLOW: String = "shallow"
+ val COPY_TYPE_DEEP: String = "deep"
+ val CONTENT: String = "content"
+ val IMAGE_SUFFIX: String = ".img"
+ val ERR_INVALID_CONTENT_ID: String = "ERR_INVALID_CONTENT_ID"
+ val EDIT_MODE: String = "edit"
+ val CONTENT_IMAGE_OBJECT_TYPE = "ContentImage"
+ val ERR_CONTENT_NOT_DRAFT = "ERR_CONTENT_NOT_DRAFT"
+ val COLLECTION_SCHEMA_NAME: String = "collection"
+ val IDENTIFIERS: String = "identifiers"
+ val PACKAGE_VERSION: String = "pkgVersion"
+ val CHANNEL: String = "channel"
+ val ERR_CONTENT_RETIRE: String = "ERR_CONTENT_RETIRE"
+ val ARTIFACT_BASE_PATH: String = "artifactBasePath"
+ val FLAGGED: String = "Flagged"
+ val FLAG_DRAFT: String = "FlagDraft"
+ val RETIRED: String = "Retired"
+ val NODE_ID: String = "node_id"
+ val VERSIONING: String = "versioning"
+ val DISABLE: String = "disable"
+ val LAST_STATUS_CHANGED_ON: String = "lastStatusChangedOn"
+ val HIERARCHY_PREFIX: String = "hierarchy_"
+ val ERR_INVALID_CONTENT: String = "ERR_INVALID_CONTENT"
+ val CONTENT_OBJECT_TYPE: String = "Content"
+ val LAST_UPDATED_ON:String = "lastUpdatedOn"
+ val VERSION:String = "version"
+ val COPY_SCHEME:String = "copyScheme"
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/CopyManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/CopyManager.scala
new file mode 100644
index 000000000..11f31cf68
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/CopyManager.scala
@@ -0,0 +1,304 @@
+package org.sunbird.content.util
+
+
+import java.io.{File, IOException}
+import java.net.URL
+import java.util
+import java.util.UUID
+import java.util.concurrent.CompletionException
+
+import org.apache.commons.collections.CollectionUtils
+import org.apache.commons.collections4.MapUtils
+import org.apache.commons.io.{FileUtils, FilenameUtils}
+import org.apache.commons.lang.StringUtils
+import org.sunbird.models.UploadParams
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.Platform
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ClientException
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.common.Identifier
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.schema.DefinitionNode
+import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
+import org.sunbird.managers.{HierarchyManager, UpdateHierarchyManager}
+import org.sunbird.mimetype.factory.MimeTypeManagerFactory
+import org.sunbird.mimetype.mgr.impl.H5PMimeTypeMgrImpl
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+
+object CopyManager {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ private val TEMP_FILE_LOCATION = Platform.getString("content.upload.temp_location", "/tmp/content")
+ private val metadataNotTobeCopied = Platform.config.getStringList("content.copy.props_to_remove")
+ private val invalidStatusList: util.List[String] = Platform.getStringList("content.copy.invalid_statusList", new util.ArrayList[String]())
+ private val invalidContentTypes: util.List[String] = Platform.getStringList("content.copy.invalid_contentTypes", new util.ArrayList[String]())
+ private val originMetadataKeys: util.List[String] = Platform.getStringList("content.copy.origin_data", new util.ArrayList[String]())
+ private val internalHierarchyProps = List("identifier", "parent", "index", "depth")
+ private val restrictedMimeTypesForUpload = List("application/vnd.ekstep.ecml-archive","application/vnd.ekstep.content-collection")
+
+ implicit val ss: StorageService = new StorageService
+
+ private var copySchemeMap: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]()
+
+ def copy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ request.getContext.put(ContentConstants.COPY_SCHEME, request.getRequest.getOrDefault(ContentConstants.COPY_SCHEME, ""))
+ validateRequest(request)
+ DataNode.read(request).map(node => {
+ validateExistingNode(node)
+ copySchemeMap = DefinitionNode.getCopySchemeContentType(request)
+ val copiedNodeFuture: Future[Node] = node.getMetadata.get(ContentConstants.MIME_TYPE) match {
+ case ContentConstants.COLLECTION_MIME_TYPE =>
+ node.setInRelations(null)
+ node.setOutRelations(null)
+ validateShallowCopyReq(node, request)
+ copyCollection(node, request)
+ case _ =>
+ node.setInRelations(null)
+ copyContent(node, request)
+ }
+ copiedNodeFuture.map(copiedNode => {
+ val response = ResponseHandler.OK()
+ response.put("node_id", new util.HashMap[String, AnyRef](){{
+ put(node.getIdentifier, copiedNode.getIdentifier)
+ }})
+ response.put(ContentConstants.VERSION_KEY, copiedNode.getMetadata.get(ContentConstants.VERSION_KEY))
+ response
+ })
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def copyContent(node: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ // cleanUpNodeRelations(node)
+ val copyCreateReq: Future[Request] = getCopyRequest(node, request)
+ copyCreateReq.map(req => {
+ DataNode.create(req).map(copiedNode => {
+ artifactUpload(node, copiedNode, request)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+
+ def copyCollection(originNode: Node, request: Request)(implicit ec:ExecutionContext, oec: OntologyEngineContext):Future[Node] = {
+ val copyType = request.getRequest.get(ContentConstants.COPY_TYPE).asInstanceOf[String]
+ copyContent(originNode, request).map(node => {
+ val req = new Request(request)
+ req.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ req.getContext.put(ContentConstants.VERSION, ContentConstants.SCHEMA_VERSION)
+ req.put(ContentConstants.ROOT_ID, request.get(ContentConstants.IDENTIFIER))
+ req.put(ContentConstants.MODE, request.get(ContentConstants.MODE))
+ HierarchyManager.getHierarchy(req).map(response => {
+ val originHierarchy = response.getResult.getOrDefault(ContentConstants.CONTENT, new java.util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ copyType match {
+ case ContentConstants.COPY_TYPE_SHALLOW => updateShallowHierarchy(request, node, originNode, originHierarchy)
+ case _ => updateHierarchy(request,node, originNode, originHierarchy, copyType)
+ }
+ }).flatMap(f=>f)
+ }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
+ }
+
+ def updateHierarchy(request: Request, node: Node, originNode: Node, originHierarchy: util.Map[String, AnyRef], copyType:String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val updateHierarchyRequest = prepareHierarchyRequest(originHierarchy, originNode, node, copyType, request)
+ val hierarchyRequest = new Request(request)
+ hierarchyRequest.putAll(updateHierarchyRequest)
+ hierarchyRequest.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ hierarchyRequest.getContext.put(ContentConstants.VERSION, ContentConstants.SCHEMA_VERSION)
+ UpdateHierarchyManager.updateHierarchy(hierarchyRequest).map(response=>node)
+ }
+
+ def updateShallowHierarchy(request: Request, node: Node, originNode: Node, originHierarchy: util.Map[String, AnyRef])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val childrenHierarchy = originHierarchy.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ val req = new Request(request)
+ req.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ req.getContext.put(ContentConstants.VERSION, ContentConstants.SCHEMA_VERSION)
+ req.getContext.put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ req.put(ContentConstants.HIERARCHY, ScalaJsonUtils.serialize(new java.util.HashMap[String, AnyRef](){{
+ put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ put(ContentConstants.CHILDREN, childrenHierarchy)
+ }}))
+ DataNode.update(req).map(node=>node)
+ }
+
+ def validateExistingNode(node: Node): Unit = {
+ if (!CollectionUtils.isEmpty(invalidContentTypes) && invalidContentTypes.contains(node.getMetadata.get(ContentConstants.CONTENT_TYPE).asInstanceOf[String]))
+ throw new ClientException(ContentConstants.CONTENT_TYPE_ASSET_CAN_NOT_COPY, "ContentType " + node.getMetadata.get(ContentConstants.CONTENT_TYPE).asInstanceOf[String] + " can not be copied.")
+ if (invalidStatusList.contains(node.getMetadata.get(ContentConstants.STATUS).asInstanceOf[String]))
+ throw new ClientException(ContentConstants.ERR_INVALID_REQUEST, "Cannot Copy content which is in " + node.getMetadata.get(ContentConstants.STATUS).asInstanceOf[String].toLowerCase + " status")
+ }
+
+ def validateRequest(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Unit = {
+ val keysNotPresent = ContentConstants.REQUIRED_KEYS.filter(key => emptyCheckFilter(request.getRequest.getOrDefault(key, "")))
+ if (keysNotPresent.nonEmpty)
+ throw new ClientException(ContentConstants.ERR_INVALID_REQUEST, "Please provide valid value for " + keysNotPresent)
+ if (StringUtils.equalsIgnoreCase(request.getRequest.getOrDefault(ContentConstants.COPY_TYPE, ContentConstants.COPY_TYPE_DEEP).asInstanceOf[String], ContentConstants.COPY_TYPE_SHALLOW) &&
+ StringUtils.isNotBlank(request.getContext.get(ContentConstants.COPY_SCHEME).asInstanceOf[String]))
+ throw new ClientException(ContentConstants.ERR_INVALID_REQUEST, "Content can not be shallow copied with copy scheme.")
+ if(StringUtils.isNotBlank(request.getContext.get(ContentConstants.COPY_SCHEME).asInstanceOf[String]) && !DefinitionNode.getAllCopyScheme(request).contains(request.getRequest.getOrDefault(ContentConstants.COPY_SCHEME, "").asInstanceOf[String]))
+ throw new ClientException(ContentConstants.ERR_INVALID_REQUEST, "Invalid copy scheme, Please provide valid copy scheme")
+ }
+
+ def emptyCheckFilter(key: AnyRef): Boolean = key match {
+ case k: String => k.asInstanceOf[String].isEmpty
+ case k: util.Map[String, AnyRef] => MapUtils.isEmpty(k.asInstanceOf[util.Map[String, AnyRef]])
+ case k: util.List[String] => CollectionUtils.isEmpty(k.asInstanceOf[util.List[String]])
+ case _ => true
+ }
+
+ def getCopyRequest(node: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Request] = {
+ val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, new util.ArrayList(), node.getObjectType.toLowerCase.replace("image", ""), ContentConstants.SCHEMA_VERSION)
+ val requestMap = request.getRequest
+ requestMap.remove(ContentConstants.MODE)
+ requestMap.remove(ContentConstants.COPY_SCHEME).asInstanceOf[String]
+ val copyType = requestMap.remove(ContentConstants.COPY_TYPE).asInstanceOf[String]
+ val originData: java.util.Map[String, AnyRef] = getOriginData(metadata, copyType)
+ cleanUpCopiedData(metadata, copyType)
+ metadata.putAll(requestMap)
+ metadata.put(ContentConstants.STATUS, "Draft")
+ metadata.put(ContentConstants.ORIGIN, node.getIdentifier)
+ metadata.put(ContentConstants.IDENTIFIER, Identifier.getIdentifier(request.getContext.get("graph_id").asInstanceOf[String], Identifier.getUniqueIdFromTimestamp))
+ if (MapUtils.isNotEmpty(originData))
+ metadata.put(ContentConstants.ORIGIN_DATA, originData)
+ request.getContext().put(ContentConstants.SCHEMA_NAME, node.getObjectType.toLowerCase.replace("image", ""))
+ updateToCopySchemeContentType(request, metadata.get(ContentConstants.CONTENT_TYPE).asInstanceOf[String], metadata)
+ val req = new Request(request)
+ req.setRequest(metadata)
+ if (StringUtils.equalsIgnoreCase("application/vnd.ekstep.ecml-archive", node.getMetadata.get("mimeType").asInstanceOf[String])) {
+ val readReq = new Request()
+ readReq.setContext(request.getContext)
+ readReq.put("identifier", node.getIdentifier)
+ readReq.put("fields", util.Arrays.asList("body"))
+ DataNode.read(readReq).map(node => {
+ if (null != node.getMetadata.get("body"))
+ req.put("body", node.getMetadata.get("body").asInstanceOf[String])
+ req
+ })
+ } else Future {req}
+ }
+
+ def getOriginData(metadata: util.Map[String, AnyRef], copyType:String): java.util.Map[String, AnyRef] = {
+ new java.util.HashMap[String, AnyRef](){{
+ putAll(originMetadataKeys.asScala.filter(key => metadata.containsKey(key)).map(key => key -> metadata.get(key)).toMap.asJava)
+ put(ContentConstants.COPY_TYPE, copyType)
+ }}
+ }
+
+ def cleanUpCopiedData(metadata: util.Map[String, AnyRef], copyType:String): util.Map[String, AnyRef] = {
+ if(StringUtils.equalsIgnoreCase(ContentConstants.COPY_TYPE_SHALLOW, copyType)) {
+ metadata.keySet().removeAll(metadataNotTobeCopied.asScala.toList.filter(str => !str.contains("dial")).asJava)
+ } else metadata.keySet().removeAll(metadataNotTobeCopied)
+ metadata
+ }
+
+ def copyURLToFile(objectId: String, fileUrl: String): File = try {
+ val file = new File(getBasePath(objectId) + File.separator + getFileNameFromURL(fileUrl))
+ FileUtils.copyURLToFile(new URL(fileUrl), file)
+ file
+ } catch {
+ case e: IOException => throw new ClientException("ERR_INVALID_FILE_URL", "Please Provide Valid File Url!")
+ }
+
+ def getBasePath(objectId: String): String = {
+ if (!StringUtils.isBlank(objectId)) TEMP_FILE_LOCATION + File.separator + System.currentTimeMillis + "_temp" + File.separator + objectId else ""
+ }
+
+ // def cleanUpNodeRelations(node: Node): Unit = {
+ // val relationsToDelete: util.List[Relation] = node.getOutRelations.asScala.filter(relation => ContentConstants.END_NODE_OBJECT_TYPES.contains(relation.getEndNodeObjectType)).toList.asJava
+ // node.getOutRelations.removeAll(relationsToDelete)
+ // }
+
+ def getUpdateRequest(request: Request, copiedNode: Node, artifactUrl: String): Request = {
+ val req = new Request()
+ val context = request.getContext
+ context.put(ContentConstants.IDENTIFIER, copiedNode.getIdentifier)
+ req.setContext(context)
+ req.put(ContentConstants.VERSION_KEY, copiedNode.getMetadata.get(ContentConstants.VERSION_KEY))
+ req.put(ContentConstants.ARTIFACT_URL, artifactUrl)
+ req
+ }
+
+ protected def getFileNameFromURL(fileUrl: String): String = if (!FilenameUtils.getExtension(fileUrl).isEmpty)
+ FilenameUtils.getBaseName(fileUrl) + "_" + System.currentTimeMillis + "." + FilenameUtils.getExtension(fileUrl) else FilenameUtils.getBaseName(fileUrl) + "_" + System.currentTimeMillis
+
+ protected def isInternalUrl(url: String): Boolean = url.contains(ss.getContainerName())
+
+ def prepareHierarchyRequest(originHierarchy: util.Map[String, AnyRef], originNode: Node, node: Node, copyType: String, request: Request):util.HashMap[String, AnyRef] = {
+ val children:util.List[util.Map[String, AnyRef]] = originHierarchy.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ if(null != children && !children.isEmpty) {
+ val nodesModified = new util.HashMap[String, AnyRef]()
+ val hierarchy = new util.HashMap[String, AnyRef]()
+ hierarchy.put(node.getIdentifier, new util.HashMap[String, AnyRef](){{
+ put(ContentConstants.CHILDREN, new util.ArrayList[String]())
+ put(ContentConstants.ROOT, true.asInstanceOf[AnyRef])
+ put(ContentConstants.CONTENT_TYPE, node.getMetadata.get(ContentConstants.CONTENT_TYPE))
+ }})
+ populateHierarchyRequest(children, nodesModified, hierarchy, node.getIdentifier, copyType, request)
+ new util.HashMap[String, AnyRef](){{
+ put(ContentConstants.NODES_MODIFIED, nodesModified)
+ put(ContentConstants.HIERARCHY, hierarchy)
+ }}
+ } else new util.HashMap[String, AnyRef]()
+ }
+
+ def populateHierarchyRequest(children: util.List[util.Map[String, AnyRef]], nodesModified: util.HashMap[String, AnyRef], hierarchy: util.HashMap[String, AnyRef], parentId: String, copyType: String, request: Request): Unit = {
+ if (null != children && !children.isEmpty) {
+ children.asScala.toList.foreach(child => {
+ val id = if ("Parent".equalsIgnoreCase(child.get(ContentConstants.VISIBILITY).asInstanceOf[String])) {
+ val identifier = UUID.randomUUID().toString
+ updateToCopySchemeContentType(request, child.get(ContentConstants.CONTENT_TYPE).asInstanceOf[String], child)
+ nodesModified.put(identifier, new util.HashMap[String, AnyRef]() {{
+ put(ContentConstants.METADATA, cleanUpCopiedData(new util.HashMap[String, AnyRef]() {{
+ putAll(child)
+ put(ContentConstants.CHILDREN, new util.ArrayList())
+ internalHierarchyProps.map(key => remove(key))
+ }}, copyType))
+ put(ContentConstants.ROOT, false.asInstanceOf[AnyRef])
+ put("isNew", true.asInstanceOf[AnyRef])
+ put("setDefaultValue", false.asInstanceOf[AnyRef])
+ }})
+ identifier
+ } else
+ child.get(ContentConstants.IDENTIFIER).asInstanceOf[String]
+ hierarchy.put(id, new util.HashMap[String, AnyRef]() {{
+ put(ContentConstants.CHILDREN, new util.ArrayList[String]())
+ put(ContentConstants.ROOT, false.asInstanceOf[AnyRef])
+ put(ContentConstants.CONTENT_TYPE, child.get(ContentConstants.CONTENT_TYPE))
+ }})
+ hierarchy.get(parentId).asInstanceOf[util.Map[String, AnyRef]].get(ContentConstants.CHILDREN).asInstanceOf[util.List[String]].add(id)
+ populateHierarchyRequest(child.get(ContentConstants.CHILDREN).asInstanceOf[util.List[util.Map[String, AnyRef]]], nodesModified, hierarchy, id, copyType, request)
+ })
+ }
+ }
+
+ def artifactUpload(node: Node, copiedNode: Node, request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val artifactUrl = node.getMetadata.getOrDefault(ContentConstants.ARTIFACT_URL, "").asInstanceOf[String]
+ val mimeType = node.getMetadata.get(ContentConstants.MIME_TYPE).asInstanceOf[String]
+ val contentType = node.getMetadata.get(ContentConstants.CONTENT_TYPE).asInstanceOf[String]
+ if (StringUtils.isNotBlank(artifactUrl) && !restrictedMimeTypesForUpload.contains(mimeType)) {
+ val mimeTypeManager = MimeTypeManagerFactory.getManager(contentType, mimeType)
+ val uploadFuture = if (isInternalUrl(artifactUrl)) {
+ val file = copyURLToFile(copiedNode.getIdentifier, artifactUrl)
+ if (mimeTypeManager.isInstanceOf[H5PMimeTypeMgrImpl])
+ mimeTypeManager.asInstanceOf[H5PMimeTypeMgrImpl].copyH5P(file, copiedNode)
+ else
+ mimeTypeManager.upload(copiedNode.getIdentifier, copiedNode, file, None, UploadParams())
+ } else mimeTypeManager.upload(copiedNode.getIdentifier, copiedNode, node.getMetadata.getOrDefault(ContentConstants.ARTIFACT_URL, "").asInstanceOf[String], None, UploadParams())
+ uploadFuture.map(uploadData => {
+ DataNode.update(getUpdateRequest(request, copiedNode, uploadData.getOrElse(ContentConstants.ARTIFACT_URL, "").asInstanceOf[String]))
+ }).flatMap(f => f)
+ } else Future(copiedNode)
+ }
+
+ def validateShallowCopyReq(node: Node, request: Request) = {
+ val copyType: String = request.getRequest.get("copyType").asInstanceOf[String]
+ if(StringUtils.equalsIgnoreCase("shallow", copyType) && !StringUtils.equalsIgnoreCase("Live", node.getMetadata.get("status").asInstanceOf[String]))
+ throw new ClientException(ContentConstants.ERR_INVALID_REQUEST, "Content with status " + node.getMetadata.get(ContentConstants.STATUS).asInstanceOf[String].toLowerCase + " cannot be partially (shallow) copied.")
+ //TODO: check if need to throw client exception for combination of copyType=shallow and mode=edit
+ }
+
+ def updateToCopySchemeContentType(request: Request, contentType: String, metadata: util.Map[String, AnyRef]): Unit = {
+ if (StringUtils.isNotBlank(request.getContext.getOrDefault(ContentConstants.COPY_SCHEME, "").asInstanceOf[String]))
+ metadata.put(ContentConstants.CONTENT_TYPE, copySchemeMap.getOrDefault(contentType, contentType))
+ }
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/DiscardManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/DiscardManager.scala
new file mode 100644
index 000000000..12f924a1f
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/DiscardManager.scala
@@ -0,0 +1,67 @@
+package org.sunbird.content.util
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.{JsonUtils, Platform}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException, ServerException}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.external.ExternalPropsManager
+import org.sunbird.managers.UpdateHierarchyManager.{fetchHierarchy, shouldImageBeDeleted}
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.utils.{HierarchyConstants, HierarchyErrorCodes}
+
+import scala.collection.JavaConversions._
+import scala.concurrent.{ExecutionContext, Future}
+
+object DiscardManager {
+ private val CONTENT_DISCARD_STATUS = Platform.getStringList("content.discard.status", util.Arrays.asList("Draft", "FlagDraft"))
+
+ @throws[Exception]
+ def discard(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ validateRequest(request)
+ getNodeToDiscard(request).flatMap(node => {
+ request.put(ContentConstants.IDENTIFIER, node.getIdentifier)
+ if (!CONTENT_DISCARD_STATUS.contains(node.getMetadata.get(ContentConstants.STATUS)))
+ throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "No changes to discard for content with content id: " + node.getIdentifier + " since content status isn't draft", node.getIdentifier)
+ val response = if (StringUtils.equalsIgnoreCase(node.getMetadata.getOrDefault(ContentConstants.MIME_TYPE, "").asInstanceOf[String], ContentConstants.COLLECTION_MIME_TYPE))
+ discardForCollection(node, request)
+ else
+ DataNode.deleteNode(request)
+ response.map(resp => {
+ val response = ResponseHandler.OK()
+ response.put("node_id", node.getIdentifier)
+ response.put("identifier", node.getIdentifier)
+ response.getResult.put("message", "Draft version of the content with id : " + node.getIdentifier + " is discarded")
+ response
+ })
+ })recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ private def getNodeToDiscard(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = {
+ val imageRequest = new Request(request)
+ imageRequest.put(ContentConstants.MODE, ContentConstants.EDIT_MODE)
+ imageRequest.put(ContentConstants.IDENTIFIER, request.get(ContentConstants.IDENTIFIER))
+ DataNode.read(imageRequest)
+ }
+
+ def validateRequest(request: Request): Unit = {
+ if (StringUtils.isBlank(request.getRequest.getOrDefault(ContentConstants.IDENTIFIER, "").asInstanceOf[String])
+ || StringUtils.endsWith(request.getRequest.getOrDefault(ContentConstants.IDENTIFIER, "").asInstanceOf[String], ContentConstants.IMAGE_SUFFIX))
+ throw new ClientException(ContentConstants.ERR_INVALID_CONTENT_ID, "Please provide valid content identifier")
+ }
+
+
+ private def discardForCollection(node: Node, request: Request)(implicit executionContext: ExecutionContext, oec: OntologyEngineContext): Future[java.lang.Boolean] = {
+ request.put(ContentConstants.IDENTIFIERS, if (node.getMetadata.containsKey(ContentConstants.PACKAGE_VERSION)) List(node.getIdentifier) else List(node.getIdentifier, node.getIdentifier + ContentConstants.IMAGE_SUFFIX))
+ request.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ oec.graphService.deleteExternalProps(request).map(resp => DataNode.deleteNode(request)).flatMap(f => f)
+ }
+
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/FlagManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/FlagManager.scala
new file mode 100644
index 000000000..c42d4e48d
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/FlagManager.scala
@@ -0,0 +1,111 @@
+package org.sunbird.content.util
+
+import java.util
+import java.util.concurrent.CompletionException
+import org.apache.commons.collections.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.{DateUtils, JsonUtils}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.external.ExternalPropsManager
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.NodeUtil
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.utils.HierarchyConstants
+import scala.concurrent.{ExecutionContext, Future}
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+
+object FlagManager {
+ private val FLAGGABLE_STATUS: util.List[String] = util.Arrays.asList("Live", "Unlisted", "Flagged")
+ private val COLLECTION_SCHEMA_NAME = "collection"
+ private val COLLECTION_CACHE_KEY_PREFIX = "hierarchy_"
+
+ def flag(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+
+ DataNode.read(request).map(node => {
+ val flagReasons: util.List[String] = request.get("flagReasons").asInstanceOf[util.ArrayList[String]]
+ val flaggedBy: String = request.get("flaggedBy").asInstanceOf[String]
+ val flags: util.List[String] = request.get("flags").asInstanceOf[util.ArrayList[String]]
+
+ val metadata: util.Map[String, Object] = NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), request.getContext.get("version").asInstanceOf[String])//node.getMetadata.asInstanceOf[util.HashMap[String, Object]]
+ val status: String = metadata.get("status").asInstanceOf[String]
+ val versionKey = node.getMetadata.get("versionKey").asInstanceOf[String]
+ request.put("identifier", node.getIdentifier)
+
+ if (!FLAGGABLE_STATUS.contains(status))
+ throw new ClientException("ERR_CONTENT_NOT_FLAGGABLE", "Unpublished Content " + node.getIdentifier + " cannot be flagged")
+
+ val flaggedByList: util.List[String] = if(StringUtils.isNotBlank(flaggedBy)) util.Arrays.asList(flaggedBy) else new util.ArrayList[String]
+ if (StringUtils.isNotEmpty(flaggedBy))
+ request.put("flaggedBy", addDataIntoList(flaggedByList, metadata, "flaggedBy"))
+ request.put("lastUpdatedBy", flaggedBy)
+ request.put("flags", flags)
+ request.put("status", "Flagged")
+ request.put("lastFlaggedOn", DateUtils.formatCurrentDate())
+ if (CollectionUtils.isNotEmpty(flagReasons))
+ request.put("flagReasons", addDataIntoList(flagReasons, metadata, "flagReasons"))
+ request.getContext.put("versioning", "disable")
+ request.put("versionKey", versionKey)
+ updateContentFlag(node, request).map(flaggedNode => {
+ val response = ResponseHandler.OK
+ val identifier: String = flaggedNode.getIdentifier
+ response.put("node_id", identifier)
+ response.put("identifier", identifier)
+ response.put("versionKey", flaggedNode.getMetadata.get("versionKey"))
+ response
+ })
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def updateCollection(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ fetchHierarchy(request).map(hierarchyString => {
+ if (!hierarchyString.asInstanceOf[String].isEmpty) {
+ val hierarchyMap = JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[util.HashMap[String, AnyRef]])
+ hierarchyMap.put("lastUpdatedBy", request.get("flaggedBy"))
+ hierarchyMap.put("flaggedBy", request.get("flaggedBy"))
+ hierarchyMap.put("flags", request.get("flags"))
+ hierarchyMap.put("status", request.get("status"))
+ hierarchyMap.put("lastFlaggedOn", request.get("lastFlaggedOn"))
+ hierarchyMap.put("flagReasons", request.get("flagReasons"))
+
+ request.put(HierarchyConstants.HIERARCHY, JsonUtils.serialize(hierarchyMap))
+ }
+ val updateNode = DataNode.update(request)
+ updateNode
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ private def fetchHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Any] = {
+ oec.graphService.readExternalProps(request, List(HierarchyConstants.HIERARCHY)).map(resp => {
+ resp.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String]
+ }) recover { case e: ResourceNotFoundException => TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + request.get(HierarchyConstants.IDENTIFIER)) }
+ }
+
+ def updateContentFlag(node: Node, request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] ={
+ val mimeType: String = node.getMetadata.get("mimeType").asInstanceOf[String]
+ RedisCache.delete(node.getIdentifier)
+ RedisCache.delete(COLLECTION_CACHE_KEY_PREFIX + node.getIdentifier)
+ if(StringUtils.equalsIgnoreCase(mimeType, HierarchyConstants.COLLECTION_MIME_TYPE)){
+ request.getContext().put("schemaName", COLLECTION_SCHEMA_NAME)
+ updateCollection(request)
+ }else
+ DataNode.update(request)
+ }
+
+ def addDataIntoList(dataList: util.List[String], metadata: util.Map[String, Object], key: String): util.List[String] = {
+ val existingData = metadata.getOrDefault(key, new util.ArrayList[String])//.asInstanceOf[util.ArrayList[String]]
+ val existingDataList = {if(existingData.isInstanceOf[Array[String]]) existingData.asInstanceOf[Array[String]].toList.asJava else if (existingData.isInstanceOf[util.List[String]]) existingData.asInstanceOf[util.List[String]] else new util.ArrayList[String]}
+ val responseDataList = new util.ArrayList[String]
+ responseDataList.addAll(existingDataList)
+ if (CollectionUtils.isEmpty(responseDataList)) {
+ dataList
+ }else{
+ responseDataList.addAll(dataList)
+ new util.ArrayList[String](responseDataList.toSet)
+ }
+ }
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/LicenseConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/LicenseConstants.scala
new file mode 100644
index 000000000..558026626
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/LicenseConstants.scala
@@ -0,0 +1,8 @@
+package org.sunbird.content.util
+
+object LicenseConstants {
+ val CREATE_LICENSE: String = "createLicense"
+ val READ_LICENSE: String = "readLicense"
+ val UPDATE_LICENSE: String = "updateLicense"
+ val RETIRE_LICENSE: String = "retireLicense"
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/RetireManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/RetireManager.scala
new file mode 100644
index 000000000..cbd8e4eba
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/RetireManager.scala
@@ -0,0 +1,111 @@
+package org.sunbird.content.util
+
+import java.util
+import java.util.{Date, UUID}
+
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.{DateUtils, JsonUtils, Platform}
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException, ServerException}
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.external.ExternalPropsManager
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.ScalaJsonUtils
+import org.sunbird.kafka.client.KafkaClient
+import org.sunbird.parseq.Task
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.utils.HierarchyConstants
+
+import scala.collection.JavaConversions._
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.{ExecutionContext, Future}
+
+object RetireManager {
+ val finalStatus: util.List[String] = util.Arrays.asList("Flagged", "Live", "Unlisted")
+ private val kfClient = new KafkaClient
+
+ def retire(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ validateRequest(request)
+ getNodeToRetire(request).flatMap(node => {
+ val updateMetadataMap = Map(ContentConstants.STATUS -> "Retired", HierarchyConstants.LAST_UPDATED_ON -> DateUtils.formatCurrentDate, HierarchyConstants.LAST_STATUS_CHANGED_ON -> DateUtils.formatCurrentDate)
+ val futureList = Task.parallel[Response](
+ handleCollectionToRetire(node, request, updateMetadataMap),
+ updateNodesToRetire(request, mapAsJavaMap[String,AnyRef](updateMetadataMap)))
+ futureList.map(f => {
+ val response = ResponseHandler.OK()
+ response.put(ContentConstants.IDENTIFIER, request.get(ContentConstants.IDENTIFIER))
+ response.put("node_id", request.get(ContentConstants.IDENTIFIER))
+ })
+ })
+ }
+
+ private def getNodeToRetire(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Node] = DataNode.read(request).map(node => {
+ if (StringUtils.equalsIgnoreCase("Retired", node.getMetadata.get(ContentConstants.STATUS).asInstanceOf[String]))
+ throw new ClientException(ContentConstants.ERR_CONTENT_RETIRE, "Content with Identifier " + node.getIdentifier + " is already Retired.")
+ node
+ })
+
+ private def validateRequest(request: Request) = {
+ val contentId: String = request.get(ContentConstants.IDENTIFIER).asInstanceOf[String]
+ if (StringUtils.isBlank(contentId) || StringUtils.endsWithIgnoreCase(contentId, HierarchyConstants.IMAGE_SUFFIX))
+ throw new ClientException(ContentConstants.ERR_INVALID_CONTENT_ID, "Please Provide Valid Content Identifier.")
+ }
+
+ private def updateNodesToRetire(request: Request, updateMetadataMap: util.Map[String, AnyRef])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ RedisCache.delete(request.get(ContentConstants.IDENTIFIER).asInstanceOf[String])
+ val updateReq = new Request(request)
+ updateReq.put(ContentConstants.IDENTIFIERS, java.util.Arrays.asList(request.get(ContentConstants.IDENTIFIER).asInstanceOf[String], request.get(ContentConstants.IDENTIFIER).asInstanceOf[String] + HierarchyConstants.IMAGE_SUFFIX))
+ updateReq.put(ContentConstants.METADATA, updateMetadataMap)
+ DataNode.bulkUpdate(updateReq).map(node => ResponseHandler.OK())
+ }
+
+
+ private def handleCollectionToRetire(node: Node, request: Request, updateMetadataMap: Map[String, AnyRef])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ if (StringUtils.equalsIgnoreCase(ContentConstants.COLLECTION_MIME_TYPE, node.getMetadata.get(ContentConstants.MIME_TYPE).asInstanceOf[String]) && finalStatus.contains(node.getMetadata.get(ContentConstants.STATUS))) {
+ RedisCache.delete("hierarchy_" + node.getIdentifier)
+ val req = new Request(request)
+ req.getContext.put(ContentConstants.SCHEMA_NAME, ContentConstants.COLLECTION_SCHEMA_NAME)
+ req.put(ContentConstants.IDENTIFIER, request.get(ContentConstants.IDENTIFIER))
+ oec.graphService.readExternalProps(req, List(HierarchyConstants.HIERARCHY)).flatMap(resp => {
+ val hierarchyString = resp.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String]
+ if (StringUtils.isNotBlank(hierarchyString)) {
+ val hierarchyMap = JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])
+ val childIds = getChildrenIdentifiers(hierarchyMap)
+ if (CollectionUtils.isNotEmpty(childIds)) {
+ val topicName = Platform.getString("kafka.topics.graph.event", "sunbirddev.learning.graph.events")
+ childIds.foreach(id => kfClient.send(ScalaJsonUtils.serialize(getLearningGraphEvent(request, id)), topicName))
+ RedisCache.delete(childIds.map(id => "hierarchy_" + id): _*)
+ }
+ hierarchyMap.putAll(updateMetadataMap)
+ req.put(HierarchyConstants.HIERARCHY, ScalaJsonUtils.serialize(hierarchyMap))
+ oec.graphService.saveExternalProps(req)
+ } else Future(ResponseHandler.OK())
+ }) recover { case e: ResourceNotFoundException =>
+ TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + node.getIdentifier)
+ throw new ServerException("ERR_CONTENT_RETIRE", "Unable to fetch Hierarchy for Root Node: [" + node.getIdentifier + "]")
+ }
+ } else Future(ResponseHandler.OK())
+ }
+
+
+ private def getChildrenIdentifiers(hierarchyMap: util.HashMap[String, AnyRef]): util.List[String] = {
+ val childIds: ListBuffer[String] = ListBuffer[String]()
+ addChildIds(hierarchyMap.getOrElse(HierarchyConstants.CHILDREN, new util.ArrayList[util.HashMap[String, AnyRef]]()).asInstanceOf[util.ArrayList[util.HashMap[String, AnyRef]]], childIds)
+ bufferAsJavaList(childIds)
+ }
+
+ private def addChildIds(childrenMaps: util.ArrayList[util.HashMap[String, AnyRef]], childrenIds: ListBuffer[String]): Unit = {
+ if (CollectionUtils.isNotEmpty(childrenMaps)) {
+ childrenMaps.filter(child => StringUtils.equalsIgnoreCase(HierarchyConstants.PARENT, child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])).foreach(child => {
+ childrenIds += child.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String]
+ addChildIds(child.get(HierarchyConstants.CHILDREN).asInstanceOf[util.ArrayList[util.HashMap[String, AnyRef]]], childrenIds)
+ })
+ }
+ }
+
+ private def getLearningGraphEvent(request: Request, id: String): Map[String, Any] = Map("ets" -> System.currentTimeMillis(), "channel" -> request.getContext.get(ContentConstants.CHANNEL), "mid" -> UUID.randomUUID.toString, "nodeType" -> "DATA_NODE", "userId" -> "Ekstep", "createdOn" -> DateUtils.format(new Date()), "objectType" -> "Content", "nodeUniqueId" -> id, "operationType" -> "DELETE", "graphId" -> request.getContext.get("graph_id"))
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/util/ChannelConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/util/ChannelConstants.scala
new file mode 100644
index 000000000..e49708593
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/util/ChannelConstants.scala
@@ -0,0 +1,23 @@
+package org.sunbird.util
+
+object ChannelConstants {
+ val DEFAULT_LICENSE: String = "defaultLicense"
+ val NODE_ID: String = "node_id"
+ val CHANNEL_LICENSE_CACHE_PREFIX: String = "channel_"
+ val CHANNEL_LICENSE_CACHE_SUFFIX: String = "_license"
+ val LICENSE_REDIS_KEY: String = "edge_license"
+ val CONTENT_PRIMARY_CATEGORIES: String = "contentPrimaryCategories"
+ val COLLECTION_PRIMARY_CATEGORIES: String = "collectionPrimaryCategories"
+ val ASSET_PRIMARY_CATEGORIES: String = "assetPrimaryCategories"
+ val CONTENT: String = "content"
+ val NAME: String = "name"
+ val OBJECT_CATEGORY: String = "ObjectCategory"
+ val objectCategoryDefinitionKey = "ObjectCategoryDefinition"
+ val ERR_VALIDATING_PRIMARY_CATEGORY: String = "ERR_VALIDATING_PRIMARY_CATEGORY"
+ val CONTENT_ADDITIONAL_CATEGORIES: String = "contentAdditionalCategories"
+ val COLLECTION_ADDITIONAL_CATEGORIES: String = "collectionAdditionalCategories"
+ val ASSET_ADDITIONAL_CATEGORIES: String = "assetAdditionalCategories"
+ val categoryKeyList: List[String] = List(CONTENT_PRIMARY_CATEGORIES, COLLECTION_PRIMARY_CATEGORIES, ASSET_PRIMARY_CATEGORIES,
+ CONTENT_ADDITIONAL_CATEGORIES, COLLECTION_ADDITIONAL_CATEGORIES, ASSET_ADDITIONAL_CATEGORIES)
+
+}
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/util/HttpUtil.scala b/content-api/content-actors/src/main/scala/org/sunbird/util/HttpUtil.scala
new file mode 100644
index 000000000..9284421ae
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/util/HttpUtil.scala
@@ -0,0 +1,35 @@
+package org.sunbird.util
+
+import com.mashape.unirest.http.Unirest
+import scala.collection.JavaConverters._
+
+/**
+ *
+ * We need to move this class to platform-core.
+ */
+
+case class HTTPResponse(status: Int, body: String) extends Serializable
+
+class HttpUtil extends Serializable {
+
+ def get(url: String, headers: Map[String, String] = Map[String, String]("Content-Type"->"application/json")): HTTPResponse = {
+ val response = Unirest.get(url).headers(headers.asJava).asString()
+ HTTPResponse(response.getStatus, response.getBody)
+ }
+
+ def post(url: String, requestBody: String, headers: Map[String, String] = Map[String, String]("Content-Type"->"application/json")): HTTPResponse = {
+ val response = Unirest.post(url).headers(headers.asJava).body(requestBody).asString()
+ HTTPResponse(response.getStatus, response.getBody)
+ }
+
+ def put(url: String, requestBody: String, headers: Map[String, String] = Map[String, String]("Content-Type"->"application/json")): HTTPResponse = {
+ val response = Unirest.put(url).headers(headers.asJava).body(requestBody).asString()
+ HTTPResponse(response.getStatus, response.getBody)
+ }
+
+ def patch(url: String, requestBody: String, headers: Map[String, String] = Map[String, String]("Content-Type"->"application/json")): HTTPResponse = {
+ val response = Unirest.patch(url).headers(headers.asJava).body(requestBody).asString()
+ HTTPResponse(response.getStatus, response.getBody)
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/main/scala/org/sunbird/util/RequestUtil.scala b/content-api/content-actors/src/main/scala/org/sunbird/util/RequestUtil.scala
new file mode 100644
index 000000000..e93949153
--- /dev/null
+++ b/content-api/content-actors/src/main/scala/org/sunbird/util/RequestUtil.scala
@@ -0,0 +1,21 @@
+package org.sunbird.util
+
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ClientException
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.schema.DefinitionNode
+
+import scala.concurrent.ExecutionContext
+
+object RequestUtil {
+
+ def restrictProperties(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Unit = {
+ val graphId = request.getContext.getOrDefault("graph_id","").asInstanceOf[String]
+ val version = request.getContext.getOrDefault("version","").asInstanceOf[String]
+ val objectType = request.getContext.getOrDefault("objectType", "").asInstanceOf[String]
+ val schemaName = request.getContext.getOrDefault("schemaName","").asInstanceOf[String]
+ val operation = request.getOperation.toLowerCase.replace(objectType.toLowerCase, "")
+ val restrictedProps =DefinitionNode.getRestrictedProperties(graphId, version, operation, schemaName)
+ if (restrictedProps.exists(prop => request.getRequest.containsKey(prop))) throw new ClientException("ERROR_RESTRICTED_PROP", "Properties in list " + restrictedProps.mkString("[", ", ", "]") + " are not allowed in request")
+ }
+}
diff --git a/learning-api/hierarchy-manager/src/test/resources/application.conf b/content-api/content-actors/src/test/resources/application.conf
similarity index 92%
rename from learning-api/hierarchy-manager/src/test/resources/application.conf
rename to content-api/content-actors/src/test/resources/application.conf
index 45c7a335f..06d8e8d15 100644
--- a/learning-api/hierarchy-manager/src/test/resources/application.conf
+++ b/content-api/content-actors/src/test/resources/application.conf
@@ -407,21 +407,6 @@ telemetry.search.topn=5
installation.id=ekstep
-learning.content.copy.invalid_status_list=["Flagged","FlaggedDraft","FraggedReview","Retired", "Processing"]
-learning.content.copy.props_to_remove=["downloadUrl", "artifactUrl", "variants",
- "createdOn", "collections", "children", "lastUpdatedOn", "SYS_INTERNAL_LAST_UPDATED_ON",
- "versionKey", "s3Key", "status", "pkgVersion", "toc_url", "mimeTypesCount",
- "contentTypesCount", "leafNodesCount", "childNodes", "prevState", "lastPublishedOn",
- "flagReasons", "compatibilityLevel", "size", "publishChecklist", "publishComment",
- "LastPublishedBy", "rejectReasons", "rejectComment", "gradeLevel", "subject",
- "medium", "board", "topic", "purpose", "subtopic", "contentCredits",
- "owner", "collaborators", "creators", "contributors", "badgeAssertions", "dialcodes",
- "concepts", "keywords", "reservedDialcodes", "dialcodeRequired", "leafNodes"]
-
-# Metadata to be added to copied content from origin
-learning.content.copy.origin_data=["name", "author", "license", "organisation"]
-
-learning.content.type.not.copied.list=["Asset"]
channel.default="in.ekstep"
@@ -431,7 +416,7 @@ dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/sear
dialcode.api.authorization=auth_key
# Language-Code Configuration
-language.graph_ids=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
# Kafka send event to topic enable
kafka.topic.send.enable=false
@@ -460,19 +445,18 @@ publish.collection.fullecar.disable=true
cassandra.lp.consistency.level=QUORUM
-content.tagging.backward_enable=false
-content.tagging.property="subject,medium"
+
content.nested.fields="badgeAssertions,targets,badgeAssociations"
content.cache.ttl=86400
-content.cache.enable=true
-collection.cache.enable=true
+content.cache.enable=false
+collection.cache.enable=false
content.discard.status=["Draft","FlagDraft"]
framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
framework.cache.ttl=86400
-framework.cache.read=true
+framework.cache.read=false
# Max size(width/height) of thumbnail in pixels
@@ -482,4 +466,46 @@ schema.base_path="../../schemas/"
content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"]
collection.keyspace = "hierarchy_store"
-content.keyspace = "content_store"
\ No newline at end of file
+content.keyspace = "content_store"
+objectcategorydefinition.keyspace=category_store
+
+
+collection.image.migration.enabled=true
+
+
+
+cloud_storage.upload.url.ttl=600
+
+content.copy.invalid_statusList=["Flagged","FlaggedDraft","FraggedReview","Retired", "Processing"]
+content.copy.props_to_remove=["downloadUrl", "artifactUrl", "variants",
+ "createdOn", "collections", "children", "lastUpdatedOn", "SYS_INTERNAL_LAST_UPDATED_ON",
+ "versionKey", "s3Key", "status", "pkgVersion", "toc_url", "mimeTypesCount",
+ "contentTypesCount", "leafNodesCount", "childNodes", "prevState", "lastPublishedOn",
+ "flagReasons", "compatibilityLevel", "size", "publishChecklist", "publishComment",
+ "LastPublishedBy", "rejectReasons", "rejectComment", "gradeLevel", "subject",
+ "medium", "board", "topic", "purpose", "subtopic", "contentCredits",
+ "owner", "collaborators", "creators", "contributors", "badgeAssertions", "dialcodes",
+ "concepts", "keywords", "reservedDialcodes", "dialcodeRequired", "leafNodes", "sYS_INTERNAL_LAST_UPDATED_ON", "prevStatus", "lastPublishedBy", "streamingUrl"]
+
+content.copy.origin_data=["name", "author", "license", "organisation"]
+content.h5p.library.path="https://s3.ap-south-1.amazonaws.com/ekstep-public-dev/content/templates/h5p-library-latest.zip"
+
+# DIAL Link
+dial_service {
+ api {
+ base_url = "https://qa.ekstep.in/api"
+ auth_key = "auth_key"
+ }
+}
+content.link_dialcode.validation=true
+content.link_dialcode.max_limit=10
+# This is added to handle large artifacts sizes differently
+content.artifact.size.for_online=209715200
+
+# Import API Config
+import {
+ request_size_limit=2
+ output_topic_name="local.auto.creation.job.request"
+ required_props=["name","code","mimeType","contentType","artifactUrl","framework", "channel"]
+}
+channel.fetch.suggested_frameworks=false
\ No newline at end of file
diff --git a/content-api/content-actors/src/test/resources/jpegImage.jpeg b/content-api/content-actors/src/test/resources/jpegImage.jpeg
new file mode 100755
index 000000000..ccef81dc8
Binary files /dev/null and b/content-api/content-actors/src/test/resources/jpegImage.jpeg differ
diff --git a/content-api/content-actors/src/test/resources/sample.pdf b/content-api/content-actors/src/test/resources/sample.pdf
new file mode 100644
index 000000000..dbf091df9
Binary files /dev/null and b/content-api/content-actors/src/test/resources/sample.pdf differ
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/channel/TestChannelManager.scala b/content-api/content-actors/src/test/scala/org/sunbird/channel/TestChannelManager.scala
new file mode 100644
index 000000000..4d58324f9
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/channel/TestChannelManager.scala
@@ -0,0 +1,132 @@
+package org.sunbird.channel
+
+import org.scalatest.{AsyncFlatSpec, Matchers}
+import org.sunbird.common.dto.Request
+
+import java.util
+import org.apache.commons.collections.CollectionUtils
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.util.{ChannelConstants, HTTPResponse, HttpUtil}
+import org.sunbird.channel.managers.ChannelManager
+import org.sunbird.common.exception.{ClientException}
+
+
+class TestChannelManager extends AsyncFlatSpec with Matchers with MockFactory {
+
+ implicit val httpUtil: HttpUtil = mock[HttpUtil]
+
+ "ChannelManager" should "return a list of frameworks from search service" in {
+ val frameworkList = ChannelManager.getAllFrameworkList()
+ assert(CollectionUtils.isNotEmpty(frameworkList))
+ }
+
+ it should "throw exception if map contains invalid language translation" in {
+ val exception = intercept[ClientException] {
+ val request = new Request()
+ request.setRequest(new util.HashMap[String, AnyRef]() {
+ {
+ put("translations", new util.HashMap[String, AnyRef]() {
+ {
+ put("tyy", "dsk")
+ }
+ })
+ }
+ })
+ ChannelManager.validateTranslationMap(request)
+ }
+ exception.getMessage shouldEqual "Please Provide Valid Language Code For translations. Valid Language Codes are : [as, bn, en, gu, hi, hoc, jun, ka, mai, mr, unx, or, san, sat, ta, te, urd, pj]"
+ }
+
+ def getRequest(): Request = {
+ val request = new Request()
+ request
+ }
+
+ it should "store license in cache" in {
+ val request = new Request()
+ request.getRequest.put("defaultLicense","license1234")
+ ChannelManager.channelLicenseCache(request, "channel_test")
+ assert(null != RedisCache.get("channel_channel_test_license"))
+ }
+
+ it should "return success for valid objectCategory" in {
+ val request = new Request()
+ request.setRequest(new util.HashMap[String, AnyRef]() {{
+ put(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("Learning Resource")}})
+ put(ChannelConstants.COLLECTION_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("Learning Resource")}})
+ put(ChannelConstants.ASSET_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("Learning Resource")}})
+ }})
+ ChannelManager.validateObjectCategory(request)
+ assert(true)
+ }
+
+ it should "throw exception for invalid objectCategory" in {
+ val exception = intercept[ClientException] {
+ val request = new Request()
+ request.setRequest(new util.HashMap[String, AnyRef]() {{
+ put(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("xyz")}})
+ put(ChannelConstants.COLLECTION_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("xyz")}})
+ put(ChannelConstants.ASSET_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("xyz")}})
+ }})
+ ChannelManager.validateObjectCategory(request)
+ }
+ exception.getMessage shouldEqual "Please provide valid : [contentPrimaryCategories,collectionPrimaryCategories,assetPrimaryCategories]"
+ }
+
+ it should "throw exception for empty objectCategory" in {
+ val exception = intercept[ClientException] {
+ val request = new Request()
+ request.setRequest(new util.HashMap[String, AnyRef]() {{
+ put(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, new util.ArrayList[String]())
+ }})
+ ChannelManager.validateObjectCategory(request)
+ }
+ exception.getMessage shouldEqual "Empty list not allowed for contentPrimaryCategories"
+ }
+
+ it should "throw exception for invalid dataType for objectCategory" in {
+ val exception = intercept[ClientException] {
+ val request = new Request()
+ request.setRequest(new util.HashMap[String, AnyRef]() {{
+ put(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, "test-string")
+ }})
+ ChannelManager.validateObjectCategory(request)
+ }
+ exception.getMessage shouldEqual "Please provide valid list for contentPrimaryCategories"
+ }
+
+ it should "add objectCategory into channel read response" in {
+ val metaDataMap: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]()
+ ChannelManager.setPrimaryAndAdditionCategories(metaDataMap)
+ assert(metaDataMap.containsKey(ChannelConstants.CONTENT_PRIMARY_CATEGORIES))
+ assert(CollectionUtils.isNotEmpty(metaDataMap.get(ChannelConstants.CONTENT_PRIMARY_CATEGORIES).asInstanceOf[util.List[String]]))
+ }
+
+ it should "not change objectCategory into channel read response" in {
+ val metaDataMap: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put(ChannelConstants.CONTENT_PRIMARY_CATEGORIES, new util.ArrayList[String]() {{add("Learning Resource")}})
+ }}
+ ChannelManager.setPrimaryAndAdditionCategories(metaDataMap)
+ assert(metaDataMap.containsKey(ChannelConstants.CONTENT_PRIMARY_CATEGORIES))
+ assert(CollectionUtils.isEqualCollection(metaDataMap.get(ChannelConstants.CONTENT_PRIMARY_CATEGORIES).asInstanceOf[util.List[String]],
+ new util.ArrayList[String]() {{add("Learning Resource")}}))
+ }
+
+ it should "return primary categories of a channel" in {
+ (httpUtil.post _).expects(*, """{"request":{"filters":{"objectType":"ObjectCategoryDefinition"},"not_exists":"channel","fields":["name","identifier","targetObjectType"]}}""", *)
+ .returning(HTTPResponse(200, """{"id":"api.v1.search","ver":"1.0","ts":"2021-02-15T05:25:54.939Z","params":{"resmsgid":"46b5e4b0-6f4e-11eb-90b0-bb9ae961ede2","msgid":"46b4d340-6f4e-11eb-90b0-bb9ae961ede2","status":"successful","err":null,"errmsg":null},"responseCode":"OK","result":{"count":24,"ObjectCategoryDefinition":[{"identifier":"obj-cat:asset_asset_all","name":"Asset","targetObjectType":"Asset","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:certasset_asset_all","name":"CertAsset","targetObjectType":"Asset","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:certificate-template_asset_all","name":"Certificate Template","targetObjectType":"Asset","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:content-playlist_content_all","name":"Content Playlist","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:content-playlist_collection_all","name":"Content Playlist","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:course_content_all","name":"Course","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:course_collection_all","name":"Course","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:course-assessment_content_all","name":"Course Assessment","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:course-unit_content_all","name":"Course Unit","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:course-unit_collection_all","name":"Course Unit","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:digital-textbook_content_all","name":"Digital Textbook","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:digital-textbook_collection_all","name":"Digital Textbook","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:etextbook_content_all","name":"eTextbook","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:explanation-content_content_all","name":"Explanation Content","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:explanation-content_collection_all","name":"Explanation Content","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:learning-resource_content_all","name":"Learning Resource","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:lesson-plan-unit_content_all","name":"Lesson Plan Unit","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:lesson-plan-unit_collection_all","name":"Lesson Plan Unit","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:plugin_content_all","name":"Plugin","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:practice-question-set_content_all","name":"Practice Question Set","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:teacher-resource_content_all","name":"Teacher Resource","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:template_content_all","name":"Template","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:textbook-unit_collection_all","name":"Textbook Unit","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:textbook-unit_content_all","name":"Textbook Unit","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"}]}}"""))
+ (httpUtil.post _).expects(*, """{"request":{"filters":{"objectType":"ObjectCategoryDefinition", "channel": "01309282781705830427"},"fields":["name","identifier","targetObjectType"]}}""", *)
+ .returning(HTTPResponse(200, """{"id":"api.v1.search","ver":"1.0","ts":"2021-02-15T05:31:41.939Z","params":{"resmsgid":"1589e430-6f4f-11eb-a956-2bb50a66d58f","msgid":"1588abb0-6f4f-11eb-a956-2bb50a66d58f","status":"successful","err":null,"errmsg":null},"responseCode":"OK","result":{"count":3,"ObjectCategoryDefinition":[{"identifier":"obj-cat:course_collection_01309282781705830427","name":"Course","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:exam-question_content_01309282781705830427","name":"Exam Question","targetObjectType":"Content","objectType":"ObjectCategoryDefinition"},{"identifier":"obj-cat:question-paper_collection_01309282781705830427","name":"Question Paper","targetObjectType":"Collection","objectType":"ObjectCategoryDefinition"}]}}"""))
+
+ val primaryCategories = ChannelManager.getChannelPrimaryCategories("01309282781705830427")
+ assert(primaryCategories.size() > 0)
+ }
+
+ it should "return additional categories" in {
+ (httpUtil.post _).expects(*, """{"request":{"filters":{"objectType":"ObjectCategory"},"fields":["name","identifier"]}}""", *)
+ .returning(HTTPResponse(200, """{"id":"api.v1.search","ver":"1.0","ts":"2021-02-15T08:06:20.058Z","params":{"resmsgid":"afba7fa0-6f64-11eb-a956-2bb50a66d58f","msgid":"afb8aae0-6f64-11eb-a956-2bb50a66d58f","status":"successful","err":null,"errmsg":null},"responseCode":"OK","result":{"ObjectCategory":[{"identifier":"obj-cat:asset","name":"Asset","objectType":"ObjectCategory"},{"identifier":"obj-cat:certificate-template","name":"Certificate Template","objectType":"ObjectCategory"},{"identifier":"obj-cat:classroom-teaching-video","name":"Classroom Teaching Video","objectType":"ObjectCategory"},{"identifier":"obj-cat:content-playlist","name":"Content Playlist","objectType":"ObjectCategory"},{"identifier":"obj-cat:course","name":"Course","objectType":"ObjectCategory"},{"identifier":"obj-cat:course-assessment","name":"Course Assessment","objectType":"ObjectCategory"}],"count":6}}"""))
+ val additionalCategories = ChannelManager.getAdditionalCategories()
+ assert(additionalCategories.size() > 0)
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/BaseSpec.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/BaseSpec.scala
new file mode 100644
index 000000000..6d0303d02
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/BaseSpec.scala
@@ -0,0 +1,45 @@
+package org.sunbird.content.actors
+
+import java.util
+import java.util.concurrent.TimeUnit
+
+import akka.actor.{ActorSystem, Props}
+import akka.testkit.TestKit
+import org.scalatest.{FlatSpec, Matchers}
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.dac.model.Node
+
+import scala.concurrent.duration.FiniteDuration
+
+class BaseSpec extends FlatSpec with Matchers {
+
+ val system = ActorSystem.create("system")
+
+ def testUnknownOperation(props: Props, request: Request)(implicit oec: OntologyEngineContext) = {
+ request.setOperation("unknown")
+ val response = callActor(request, props)
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+ def callActor(request: Request, props: Props): Response = {
+ val probe = new TestKit(system)
+ val actorRef = system.actorOf(props)
+ actorRef.tell(request, probe.testActor)
+ probe.expectMsgType[Response](FiniteDuration.apply(30, TimeUnit.SECONDS))
+ }
+
+ def getNode(objectType: String, metadata: Option[util.Map[String, AnyRef]]): Node = {
+ val node = new Node("domain", "DATA_NODE", objectType)
+ node.setGraphId("domain")
+ val nodeMetadata = metadata.getOrElse(new util.HashMap[String, AnyRef]() {{
+ put("name", "Sunbird Node")
+ put("code", "sunbird-node")
+ put("status", "Draft")
+ }})
+ node.setMetadata(nodeMetadata)
+ node.setObjectType(objectType)
+ node.setIdentifier("test_id")
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAppActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAppActor.scala
new file mode 100644
index 000000000..64bffd638
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAppActor.scala
@@ -0,0 +1,119 @@
+package org.sunbird.content.actors
+
+import akka.actor.Props
+import org.apache.hadoop.util.StringUtils
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.Request
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import java.util
+import scala.collection.JavaConverters._
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+
+class TestAppActor extends BaseSpec with MockFactory {
+
+ "AppActor" should "return failed response for 'unknown' operation" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new AppActor()), getRequest())
+ }
+
+ it should "return success response for 'create' operation" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = new Node("domain", "DATA_NODE", "App")
+ node.setIdentifier("android-org.test.sunbird.integration")
+ node.setObjectType("App")
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ val request = getRequest()
+ request.getRequest.put("name", "Test Integration App")
+ request.getRequest.put("logo", "logo url")
+ request.getRequest.put("description", "Description of Test Integration App")
+ request.getRequest.put("provider", Map("name" -> "Test Organisation", "copyright" -> "CC BY 4.0").asJava)
+ request.getRequest.put("osType", "Android")
+ request.getRequest.put("osMetadata", Map("packageId" -> "org.test.integration", "appVersion" -> "1.0", "compatibilityVer" -> "1.0").asJava)
+ request.setOperation("create")
+ val response = callActor(request, Props(new AppActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "throw client exception to have all the required properties for app register" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getRequest()
+ request.getRequest.put("name", "Test Integration App")
+ request.setOperation("create")
+ val response = callActor(request, Props(new AppActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for update" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getRequest()
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc")))
+ request.setOperation("update")
+ val response = callActor(request, Props(new AppActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("android-org.test.sunbird.integration"))
+ }
+
+ it should "return success response for read app" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(1)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ val request = getRequest()
+ request.putAll(mapAsJavaMap(Map("fields" -> "")))
+ request.setOperation("read")
+ val response = callActor(request, Props(new AppActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(StringUtils.equalsIgnoreCase(response.get("app").asInstanceOf[util.Map[String, AnyRef]].get("identifier").asInstanceOf[String], "android-org.test.sunbird.integration"))
+ assert(StringUtils.equalsIgnoreCase(response.get("app").asInstanceOf[util.Map[String, AnyRef]].get("status").asInstanceOf[String], "Draft"))
+ }
+
+ private def getRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "App")
+ put("schemaName", "app")
+ put("X-Channel-Id", "org.sunbird")
+ }
+ })
+ request.setObjectType("App")
+ request
+ }
+
+ private def getValidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("android-org.test.sunbird.integration")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("App")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "android-org.test.sunbird.integration")
+ put("status", "Draft")
+ put("name", "Test Integration App")
+ put("logo", "logo url")
+ put("description", "Description of Test Integration App")
+ put("provider", Map("name" -> "Test Organisation", "copyright" -> "CC BY 4.0").asJava)
+ put("osType", "Android")
+ put("osMetadata", Map("packageId" -> "org.test.sunbird.integration", "appVersion" -> "1.0", "compatibilityVer" -> "1.0").asJava)
+ }
+ })
+ node
+ }
+
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAssetActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAssetActor.scala
new file mode 100644
index 000000000..070451936
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestAssetActor.scala
@@ -0,0 +1,121 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.{Node, SearchCriteria}
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestAssetActor extends BaseSpec with MockFactory {
+
+ "AssetActor" should "return failed response for 'unknown' operation" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new AssetActor()), getContentRequest())
+ }
+
+ it should "return success response for 'copyAsset'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getNode()))
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response())).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234")
+ request.put("identifier","do_1234")
+ request.setOperation("copy")
+ val response = callActor(request, Props(new AssetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.getResult.containsKey("node_id"))
+ assert("test_321".equals(response.get("versionKey")))
+ }
+
+ it should "copy asset with invalid objectType, should through client exception" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getInvalidNode()))
+ val request = getContentRequest()
+ request.setOperation("copy")
+ val response = callActor(request, Props(new AssetActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ assert(response.getParams.getErrmsg == "Only asset can be copied")
+ }
+
+ private def getNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Asset")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "application/vnd.ekstep.content-archive")
+ put("status", "Live")
+ put("name", "Asset_Test")
+ put("versionKey", "test_321")
+ put("channel", "in.ekstep")
+ put("code", "Asset_Test")
+ put("contentType", "Asset")
+ put("primaryCategory", "Asset")
+ put("artifactUrl", "https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_1234/artifact/file-0130860005482086401.svg")
+ }
+ })
+ node
+ }
+
+ private def getContentRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Asset")
+ put("schemaName", "asset")
+ put("X-Channel-Id", "in.ekstep")
+ }
+ })
+ request.setObjectType("Asset")
+ request
+ }
+
+ private def getInvalidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node
+ }
+
+ def getFrameworkNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("NCF")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Framework")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(Map("name"-> "NCF")))
+ node
+ }
+
+ def getBoardNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("ncf_board_cbse")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Term")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(Map("name"-> "CBSE")))
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCategoryActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCategoryActor.scala
new file mode 100644
index 000000000..7a5014d99
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCategoryActor.scala
@@ -0,0 +1,141 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import akka.actor.Props
+import org.apache.hadoop.util.StringUtils
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.Node
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestCategoryActor extends BaseSpec with MockFactory{
+
+ "CategoryActor" should "return failed response for 'unknown' operation" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new CategoryActor()), getCategoryRequest())
+ }
+
+ it should "create a categoryNode and store it in neo4j" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getValidNode()))
+ val request = getCategoryRequest()
+ request.putAll(mapAsJavaMap(Map("name" -> "do_1234")))
+ request.setOperation("createCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("identifier").equals("cat-do_1234"))
+ }
+
+ it should "return exception for create categoryNode without name" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getCategoryRequest()
+ request.setOperation("createCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ assert(StringUtils.equalsIgnoreCase(response.get("messages").asInstanceOf[util.ArrayList[String]].get(0).asInstanceOf[String], "Required Metadata name not set"))
+ }
+
+ it should "return exception for categoryNode with identifier" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getCategoryRequest()
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("createCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ assert(StringUtils.equalsIgnoreCase(response.getParams.getErrmsg, "name will be set as identifier"))
+ }
+
+ it should "return success response for updateCategory" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getValidNode()))
+ implicit val ss = mock[StorageService]
+ val request = getCategoryRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc")))
+ request.setOperation("updateCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+
+ it should "return success response for readCategory" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(1)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getCategoryRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("fields" -> "")))
+ request.setOperation("readCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for retireCategory" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getValidNode()))
+ implicit val ss = mock[StorageService]
+ val request = getCategoryRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("retireCategory")
+ val response = callActor(request, Props(new CategoryActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getCategoryRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Category")
+ put("schemaName", "category")
+
+ }
+ })
+ request.setObjectType("Category")
+ request
+ }
+
+ private def getValidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("cat-do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Category")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "cat-do_1234")
+ put("objectType", "Category")
+ put("status", "Live")
+ put("name", "do_1234")
+ put("versionKey", "1878141")
+ }
+ })
+ node
+ }
+
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestChannelActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestChannelActor.scala
new file mode 100644
index 000000000..2a14668e4
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestChannelActor.scala
@@ -0,0 +1,125 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.channel.actors.ChannelActor
+import org.sunbird.common.dto.Request
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import scala.collection.JavaConversions._
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestChannelActor extends BaseSpec with MockFactory {
+
+ "ChannelActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new ChannelActor()), getRequest())
+ }
+
+ it should "return success response for 'createChannel' operation" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = new Node("domain", "DATA_NODE", "Channel")
+ node.setIdentifier("channel_test")
+ node.setObjectType("Channel")
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ val request = getRequest()
+ request.getRequest.put("name", "channel_test")
+ request.getRequest.put("code", "channel_test")
+ request.setOperation("createChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "throw exception code is required for createChannel" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getRequest()
+ request.getRequest.put("name", "channel_test")
+ request.setOperation("createChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+ it should "throw invalid identifier exception for channelUpdate" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = new Node("domain",mapAsJavaMap(Map("identifier" -> "channel_test", "nodeType"->"DATA_NODE", "objectType"->"Channel")))
+ node.setIdentifier("channel_test")
+ node.setObjectType("Channel")
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ val request = getRequest()
+ request.getRequest.put("name", "channel_test2")
+ request.setOperation("updateChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ }
+
+ ignore should "return success response for 'readChannel' operation" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = new Node("domain",mapAsJavaMap(Map("identifier" -> "channel_test", "nodeType"->"DATA_NODE", "objectType"->"Channel")))
+ node.setIdentifier("channel_test")
+ node.setObjectType("Channel")
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ val request = getRequest()
+ request.getRequest.put("identifier", "channel_test")
+ request.setOperation("readChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+// assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'updateChannel' operation" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Channel", None)
+ node.setObjectType("Channel")
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ (graphDB.upsertNode(_:String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getRequest()
+ request.getContext.put("identifier", "channel_test");
+ request.getRequest.put("name", "channel_test")
+ request.setOperation("updateChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'retireChannel' operation" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Channel", None)
+ node.setObjectType("Channel")
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ (graphDB.upsertNode(_:String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getRequest()
+ request.getContext.put("identifier", "channel_test");
+ request.getRequest.put("identifier", "channel_test")
+ request.setOperation("retireChannel")
+ val response = callActor(request, Props(new ChannelActor()))
+ println("Response: retire: " + response)
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Channel")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "channel")
+ }
+ })
+ request.setObjectType("Channel")
+ request
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCollectionActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCollectionActor.scala
new file mode 100644
index 000000000..6782b4308
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestCollectionActor.scala
@@ -0,0 +1,30 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import akka.actor.Props
+import org.sunbird.common.dto.Request
+import org.sunbird.graph.OntologyEngineContext
+
+class TestCollectionActor extends BaseSpec {
+
+ "CollectionActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation( Props(new CollectionActor()), getCollectionRequest())
+ }
+
+ private def getCollectionRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "collection")
+
+ }
+ })
+ request.setObjectType("Collection")
+ request
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala
new file mode 100644
index 000000000..b2b8154cf
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala
@@ -0,0 +1,456 @@
+package org.sunbird.content.actors
+
+import java.io.File
+import java.util
+
+import org.sunbird.graph.dac.model.{Node, SearchCriteria}
+import akka.actor.Props
+import com.google.common.io.Resources
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.{HttpUtil, JsonUtils}
+import org.sunbird.common.dto.{Property, Request, Response, ResponseHandler}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.utils.ScalaJsonUtils
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.kafka.client.KafkaClient
+
+import scala.collection.JavaConversions._
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestContentActor extends BaseSpec with MockFactory {
+
+ "ContentActor" should "return failed response for 'unknown' operation" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new ContentActor()), getContentRequest())
+ }
+
+ it should "validate input before creating content" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getContentRequest()
+ val content = mapAsJavaMap(Map("name" -> "New Content", "code" -> "1234", "mimeType"-> "application/pdf", "contentType" -> "Resource",
+ "framework" -> "NCF", "organisationBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}}))
+ request.put("content", content)
+ assert(true)
+ val response = callActor(request, Props(new ContentActor()))
+ println("Response: " + JsonUtils.serialize(response))
+
+ }
+
+ it should "create a node and store it in neo4j" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ // Uncomment below line if running individual file in local.
+ //(graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response()))
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getValidNode()))
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(new util.ArrayList[Node]() {
+ {
+ add(getBoardNode())
+ }
+ }))
+ val request = getContentRequest()
+ request.getRequest.putAll( mapAsJavaMap(Map("channel"-> "in.ekstep","name" -> "New Content", "code" -> "1234", "mimeType"-> "application/vnd.ekstep.content-collection", "contentType" -> "Course", "primaryCategory" -> "Learning Resource", "channel" -> "in.ekstep", "targetBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}})))
+ request.setOperation("createContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("versionKey") != null)
+ }
+
+ it should "create a plugin node and store it in neo4j" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDefinitionNode())).anyNumberOfTimes()
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getValidNode()))
+ val request = getContentRequest()
+ request.getRequest.putAll( mapAsJavaMap(Map("name" -> "New Content", "code" -> "1234", "mimeType"-> "application/vnd.ekstep.plugin-archive", "contentType" -> "Course", "primaryCategory" -> "Learning Resource", "channel" -> "in.ekstep", "framework"-> "NCF", "organisationBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}})))
+ request.setOperation("createContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("versionKey") != null)
+ }
+
+ it should "create a plugin node with invalid request, should through client exception" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ val request = getContentRequest()
+ request.getRequest.putAll( mapAsJavaMap(Map("name" -> "New Content", "mimeType"-> "application/vnd.ekstep.plugin-archive", "contentType" -> "Course", "primaryCategory" -> "Learning Resource", "channel" -> "in.ekstep")))
+ request.setOperation("createContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ it should "generate and return presigned url" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getValidNode()))
+ implicit val ss = mock[StorageService]
+ (ss.getSignedURL(_: String, _: Option[Int], _: Option[String])).expects(*, *, *).returns("cloud store url")
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("fileName" -> "presigned_url", "filePath" -> "/data/cloudstore/", "type" -> "assets", "identifier" -> "do_1234")))
+ request.setOperation("uploadPreSignedUrl")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("pre_signed_url") != null)
+ assert(response.get("url_expiry") != null)
+ }
+
+ it should "discard node in draft state should return success" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getValidNodeToDiscard()))
+ (graphDB.deleteNode(_: String, _: String, _: Request)).expects(*, *, *).returns(Future(true))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.getResponseCode == ResponseCode.OK)
+ assert(response.get("identifier") == "do_12346")
+ assert(response.get("message") == "Draft version of the content with id : do_12346 is discarded")
+
+ }
+
+ it should "discard node in Live state should return client error" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(1)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getInValidNodeToDiscard()))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ it should "return client error response for retireContent" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234.img")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234.img")))
+ request.setOperation("retireContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ private def getContentRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+ put("X-Channel-Id", "in.ekstep")
+ }
+ })
+ request.setObjectType("Content")
+ request
+ }
+
+ private def getValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("name", "Node To discard")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ private def getInValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("mimeType", "application/pdf")
+ put("status", "Live")
+ put("contentType", "Resource")
+ put("name", "Node To discard")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ private def getValidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "application/vnd.ekstep.content-collection")
+ put("status", "Draft")
+ put("contentType", "Course")
+ put("name", "Course_1")
+ put("versionKey", "1878141")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ it should "return success response for retireContent" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getNode("Content", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node]))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("retireContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'readContent'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = getNode("Content", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "fields" -> "")))
+ request.setOperation("readContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'updateContent'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("test_123"))))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc", "versionKey" -> "test_123")))
+ request.setOperation("updateContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert("do_1234".equals(response.get("identifier")))
+ assert("test_123".equals(response.get("versionKey")))
+ }
+
+ it should "return client exception for 'updateContent' with invalid versionKey" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("test_xyz"))))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc", "versionKey" -> "test_123")))
+ request.setOperation("updateContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ assert("CLIENT_ERROR".equals(response.getParams.getErr))
+ assert("Invalid version Key".equals(response.getParams.getErrmsg))
+ }
+
+ it should "return client exception for 'updateContent' without versionKey" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.putAll(mapAsJavaMap(Map("description" -> "updated description","framework" -> "NCF", "organisationBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}})))
+ request.setOperation("updateContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("failed".equals(response.getParams.getStatus))
+ assert("ERR_INVALID_REQUEST".equals(response.getParams.getErr))
+ assert("Please Provide Version Key!".equals(response.getParams.getErrmsg))
+ }
+
+ it should "return success response for 'copyContent'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(node))
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response()))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "createdBy" -> "username_1",
+ "createdFor" -> new util.ArrayList[String]() {{ add("NCF2") }}, "framework" -> "NCF",
+ "organisation" -> new util.ArrayList[String]() {{ add("NCF2") }})))
+ request.setOperation("copy")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.getResult.containsKey("node_id"))
+ assert("test_321".equals(response.get("versionKey")))
+ }
+
+ it should "send events to kafka topic" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val kfClient = mock[KafkaClient]
+ val hUtil = mock[HttpUtil]
+ (oec.httpUtil _).expects().returns(hUtil)
+ val resp :Response = ResponseHandler.OK()
+ resp.put("content", new util.HashMap[String, AnyRef](){{
+ put("framework", "NCF")
+ put("artifactUrl", "http://test.com/test.pdf")
+ put("channel", "test")
+ }})
+ (hUtil.get(_: String, _: String, _: util.Map[String, String])).expects(*, *, *).returns(resp)
+ (oec.kafkaClient _).expects().returns(kfClient)
+ (kfClient.send(_: String, _: String)).expects(*, *).returns(None)
+ val request = getContentRequest()
+ request.getRequest.put("content", new util.HashMap[String, AnyRef](){{
+ put("source", "https://dock.sunbirded.org/api/content/v1/read/do_11307822356267827219477")
+ put("metadata", new util.HashMap[String, AnyRef](){{
+ put("name", "Test Content")
+ put("description", "Test Content")
+ put("mimeType", "application/pdf")
+ put("code", "test.res.1")
+ put("contentType", "Resource")
+ put("primaryCategory", "Learning Resource")
+ }})
+ }})
+ request.setOperation("importContent")
+ request.setObjectType("Content")
+ val response = callActor(request, Props(new ContentActor()))
+ assert(response.get("processId") != null)
+ }
+
+ it should "return success response for 'uploadContent' with jpeg asset" ignore {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ implicit val ss = mock[StorageService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getAssetNodeToUpload()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ (ss.uploadFile(_:String, _: File, _: Option[Boolean])).expects(*, *, *).returns(Array("do_1234", "do_1234"))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getContentRequest()
+ request.getContext.put("identifier", "do_1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "createdBy" -> "username_1",
+ "createdFor" -> new util.ArrayList[String]() {{ add("NCF2") }}, "framework" -> "NCF",
+ "organisation" -> new util.ArrayList[String]() {{ add("NCF2") }})))
+ request.put("file", new File(Resources.getResource("jpegImage.jpeg").toURI))
+ request.setOperation("uploadContent")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getAssetNodeToUpload(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Asset")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "image/jpeg")
+ put("contentType", "Asset")
+ put("name", "Asset_1")
+ put("versionKey", "test_321")
+ put("channel", "in.ekstep")
+ put("code", "Resource_1")
+ put("primaryCategory", "Asset")
+ put("versionKey", "1234")
+ }
+ })
+ node
+ }
+
+ private def getNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "application/pdf")
+ put("status", "Live")
+ put("contentType", "Resource")
+ put("name", "Resource_1")
+ put("versionKey", "test_321")
+ put("channel", "in.ekstep")
+ put("code", "Resource_1")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+
+ def getDefinitionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:learning-resource_content_in.ekstep")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String,AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:learning-resource\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"license\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+
+ def getContentSchema(): util.Map[String, AnyRef] = {
+ val schema:String = "{\n \"$id\": \"content-schema.json\",\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"title\": \"Content\",\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"status\",\n \"mimeType\",\n \"channel\",\n \"contentType\",\n \"code\",\n \"contentEncoding\",\n \"contentDisposition\",\n \"mediaType\",\n \"primaryCategory\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 5\n },\n \"code\": {\n \"type\": \"string\"\n },\n \"createdOn\": {\n \"type\": \"string\"\n },\n \"lastUpdatedOn\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"Draft\",\n \"Review\",\n \"Redraft\",\n \"Flagged\",\n \"Live\",\n \"Unlisted\",\n \"Retired\",\n \"Mock\",\n \"Processing\",\n \"FlagDraft\",\n \"FlagReview\",\n \"Failed\"\n ],\n \"default\": \"Draft\"\n },\n \"channel\": {\n \"type\": \"string\"\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/vnd.ekstep.ecml-archive\",\n \"application/vnd.ekstep.html-archive\",\n \"application/vnd.android.package-archive\",\n \"application/vnd.ekstep.content-archive\",\n \"application/vnd.ekstep.content-collection\",\n \"application/vnd.ekstep.plugin-archive\",\n \"application/vnd.ekstep.h5p-archive\",\n \"application/epub\",\n \"text/x-url\",\n \"video/x-youtube\",\n \"application/octet-stream\",\n \"application/msword\",\n \"application/pdf\",\n \"image/jpeg\",\n \"image/jpg\",\n \"image/png\",\n \"image/tiff\",\n \"image/bmp\",\n \"image/gif\",\n \"image/svg+xml\",\n \"video/avi\",\n \"video/mpeg\",\n \"video/quicktime\",\n \"video/3gpp\",\n \"video/mp4\",\n \"video/ogg\",\n \"video/webm\",\n \"audio/mp3\",\n \"audio/mp4\",\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/webm\",\n \"audio/x-wav\",\n \"audio/wav\"\n ]\n },\n \"osId\": {\n \"type\": \"string\",\n \"default\": \"org.ekstep.launcher\"\n },\n \"contentEncoding\": {\n \"type\": \"string\",\n \"enum\": [\n \"gzip\",\n \"identity\"\n ],\n \"default\": \"gzip\"\n },\n \"contentDisposition\": {\n \"type\": \"string\",\n \"enum\": [\n \"inline\",\n \"online\",\n \"attachment\",\n \"online-only\"\n ],\n \"default\": \"inline\"\n },\n \"mediaType\": {\n \"type\": \"string\",\n \"enum\": [\n \"content\",\n \"collection\",\n \"image\",\n \"video\",\n \"audio\",\n \"voice\",\n \"ecml\",\n \"document\",\n \"pdf\",\n \"text\",\n \"other\"\n ],\n \"default\": \"content\"\n },\n \"os\": {\n \"type\": \"array\",\n \"items\": {\n \"type\" : \"string\",\n \"enum\": [\n \"All\",\n \"Android\",\n \"iOS\",\n \"Windows\"\n ]\n },\n \"default\": [\"All\"]\n },\n \"minOsVersion\": {\n \"type\": \"string\"\n },\n \"compatibilityLevel\": {\n \"type\": \"number\",\n \"default\": 1\n },\n \"minGenieVersion\": {\n \"type\": \"string\"\n },\n \"minSupportedVersion\": {\n \"type\": \"string\"\n },\n \"filter\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"variants\": {\n \"type\": \"object\"\n },\n \"config\": {\n \"type\": \"object\"\n },\n \"visibility\": {\n \"type\": \"string\",\n \"enum\": [\n \"Default\",\n \"Parent\"\n ],\n \"default\": \"Default\"\n },\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\",\n \"Administrator\"\n ]\n },\n \"default\": [\"Student\"]\n },\n \"posterImage\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"badgeAssertions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"targets\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"contentCredits\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"appIcon\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"grayScaleAppIcon\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"thumbnail\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"screenshots\": {\n \"type\": \"string\"\n },\n \"format\": {\n \"type\": \"string\"\n },\n \"duration\": {\n \"type\": \"string\"\n },\n \"size\": {\n \"type\": \"number\"\n },\n \"idealScreenSize\": {\n \"type\": \"string\",\n \"enum\": [\n \"small\",\n \"normal\",\n \"large\",\n \"xlarge\",\n \"other\"\n ],\n \"default\": \"normal\"\n },\n \"idealScreenDensity\": {\n \"type\": \"string\",\n \"enum\": [\n \"ldpi\",\n \"mdpi\",\n \"hdpi\",\n \"xhdpi\",\n \"xxhdpi\",\n \"xxxhdpi\"\n ],\n \"default\": \"hdpi\"\n },\n \"releaseNotes\": {\n \"type\": \"array\"\n },\n \"pkgVersion\": {\n \"type\": \"number\"\n },\n \"semanticVersion\": {\n \"type\": \"string\"\n },\n \"versionKey\": {\n \"type\": \"string\"\n },\n \"resources\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Speaker\",\n \"Touch\",\n \"Microphone\",\n \"GPS\",\n \"Motion Sensor\",\n \"Compass\"\n ]\n }\n },\n \"downloadUrl\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"artifactUrl\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"previewUrl\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"streamingUrl\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"objects\": {\n \"type\": \"array\"\n },\n \"organization\": {\n \"type\": \"array\"\n },\n \"createdFor\": {\n \"type\": \"array\"\n },\n \"developer\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"pageNumber\": {\n \"type\": \"string\"\n },\n \"publication\": {\n \"type\": \"string\"\n },\n \"edition\": {\n \"type\": \"string\"\n },\n \"publisher\": {\n \"type\": \"string\"\n },\n \"author\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"string\"\n },\n \"attributions\": {\n \"type\": \"array\"\n },\n \"collaborators\": {\n \"type\": \"array\"\n },\n \"creators\": {\n \"type\": \"string\"\n },\n \"contributors\": {\n \"type\": \"string\"\n },\n \"voiceCredits\": {\n \"type\": \"array\"\n },\n \"soundCredits\": {\n \"type\": \"array\"\n },\n \"imageCredits\": {\n \"type\": \"array\"\n },\n \"copyright\": {\n \"type\": \"string\"\n },\n \"license\": {\n \"type\": \"string\",\n \"default\": \"CC BY 4.0\"\n },\n \"language\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"English\",\n \"Hindi\",\n \"Assamese\",\n \"Bengali\",\n \"Gujarati\",\n \"Kannada\",\n \"Malayalam\",\n \"Marathi\",\n \"Nepali\",\n \"Odia\",\n \"Punjabi\",\n \"Tamil\",\n \"Telugu\",\n \"Urdu\",\n \"Sanskrit\",\n \"Maithili\",\n \"Munda\",\n \"Santali\",\n \"Juang\",\n \"Ho\",\n \"Other\"\n ]\n },\n \"default\": [\"English\"]\n },\n \"words\": {\n \"type\": \"array\"\n },\n \"text\": {\n \"type\": \"array\"\n },\n \"forkable\": {\n \"type\": \"boolean\"\n },\n \"translatable\": {\n \"type\": \"boolean\"\n },\n \"ageGroup\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"<5\",\n \"5-6\",\n \"6-7\",\n \"7-8\",\n \"8-10\",\n \">10\",\n \"Other\"\n ]\n }\n },\n \"interactivityLevel\": {\n \"type\": \"string\",\n \"enum\": [\n \"High\",\n \"Medium\",\n \"Low\"\n ]\n },\n \"contentType\": {\n \"type\": \"string\",\n \"enum\": [\n \"Resource\",\n \"Collection\",\n \"TextBook\",\n \"LessonPlan\",\n \"Course\",\n \"Template\",\n \"Asset\",\n \"Plugin\",\n \"LessonPlanUnit\",\n \"CourseUnit\",\n \"TextBookUnit\",\n \"TeachingMethod\",\n \"PedagogyFlow\",\n \"FocusSpot\",\n \"LearningOutcomeDefinition\",\n \"PracticeQuestionSet\",\n \"CuriosityQuestionSet\",\n \"MarkingSchemeRubric\",\n \"ExplanationResource\",\n \"ExperientialResource\",\n \"ConceptMap\",\n \"SelfAssess\",\n \"PracticeResource\",\n \"eTextBook\",\n \"OnboardingResource\",\n \"ExplanationVideo\",\n \"ClassroomTeachingVideo\",\n \"ExplanationReadingMaterial\",\n \"LearningActivity\",\n \"PreviousBoardExamPapers\",\n \"LessonPlanResource\",\n \"TVLesson\"\n ]\n },\n \"resourceType\": {\n \"type\": \"string\",\n \"enum\": [\n \"Read\",\n \"Learn\",\n \"Teach\",\n \"Play\",\n \"Test\",\n \"Practice\",\n \"Experiment\",\n \"Collection\",\n \"Book\",\n \"Lesson Plan\",\n \"Course\",\n \"Theory\",\n \"Worksheet\",\n \"Practical\"\n ]\n },\n \"category\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"core\",\n \"learning\",\n \"literacy\",\n \"math\",\n \"science\",\n \"time\",\n \"wordnet\",\n \"game\",\n \"mcq\",\n \"mtf\",\n \"ftb\",\n \"library\"\n ]\n }\n },\n \"templateType\": {\n \"type\": \"string\",\n \"enum\": [\n \"story\",\n \"worksheet\",\n \"mcq\",\n \"ftb\",\n \"mtf\",\n \"recognition\",\n \"activity\",\n \"widget\",\n \"other\"\n ]\n },\n \"genre\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Picture Books\",\n \"Chapter Books\",\n \"Flash Cards\",\n \"Serial Books\",\n \"Alphabet Books\",\n \"Folktales\",\n \"Fiction\",\n \"Non-Fiction\",\n \"Poems/Rhymes\",\n \"Plays\",\n \"Comics\",\n \"Words\"\n ]\n }\n },\n \"theme\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"History\",\n \"Adventure\",\n \"Mystery\",\n \"Science\",\n \"Nature\",\n \"Art\",\n \"Music\",\n \"Funny\",\n \"Family\",\n \"Life Skills\",\n \"Scary\",\n \"School Stories\",\n \"Holidays\",\n \"Hobby\",\n \"Geography\",\n \"Rural\",\n \"Urban\"\n ]\n }\n },\n \"themes\": {\n \"type\": \"array\"\n },\n \"rating\": {\n \"type\": \"number\"\n },\n \"rating_a\": {\n \"type\": \"number\"\n },\n \"quality\": {\n \"type\": \"number\"\n },\n \"genieScore\": {\n \"type\": \"number\"\n },\n \"authoringScore\": {\n \"type\": \"number\"\n },\n \"popularity\": {\n \"type\": \"number\"\n },\n \"downloads\": {\n \"type\": \"number\"\n },\n \"launchUrl\": {\n \"type\": \"string\"\n },\n \"activity_class\": {\n \"type\": \"string\"\n },\n \"draftImage\": {\n \"type\": \"string\"\n },\n \"scaffolding\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Tutorial\",\n \"Help\",\n \"Practice\"\n ]\n }\n },\n \"feedback\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Right/Wrong\",\n \"Reflection\",\n \"Guidance\",\n \"Learn from Mistakes\",\n \"Adaptive Feedback\",\n \"Interrupts\",\n \"Rich Feedback\"\n ]\n }\n },\n \"feedbackType\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Audio\",\n \"Visual\",\n \"Textual\",\n \"Tactile\"\n ]\n }\n },\n \"teachingMode\": {\n \"type\": \"string\",\n \"enum\": [\n \"Abstract\",\n \"Concrete\",\n \"Pictorial\"\n ]\n },\n \"skills\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Listening\",\n \"Speaking\",\n \"Reading\",\n \"Writing\",\n \"Touch\",\n \"Gestures\",\n \"Draw\"\n ]\n }\n },\n \"keywords\": {\n \"type\": \"array\"\n },\n \"domain\": {\n \"type\": \"array\"\n },\n \"dialcodes\": {\n \"type\": \"array\"\n },\n \"optStatus\": {\n \"type\": \"string\",\n \"enum\": [\n \"Pending\",\n \"Processing\",\n \"Error\",\n \"Complete\"\n ]\n },\n \"description\": {\n \"type\": \"string\"\n },\n \"instructions\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"type\": \"string\"\n },\n \"oldBody\": {\n \"type\": \"string\"\n },\n \"stageIcons\": {\n \"type\": \"string\"\n },\n \"editorState\": {\n \"type\": \"string\"\n },\n \"data\": {\n \"type\": \"array\"\n },\n \"loadingMessage\": {\n \"type\": \"string\"\n },\n \"checksum\": {\n \"type\": \"string\"\n },\n \"learningObjective\": {\n \"type\": \"array\"\n },\n \"createdBy\": {\n \"type\": \"string\"\n },\n \"creator\": {\n \"type\": \"string\"\n },\n \"reviewer\": {\n \"type\": \"string\"\n },\n \"lastUpdatedBy\": {\n \"type\": \"string\"\n },\n \"lastSubmittedBy\": {\n \"type\": \"string\"\n },\n \"lastSubmittedOn\": {\n \"type\": \"string\"\n },\n \"lastPublishedBy\": {\n \"type\": \"string\"\n },\n \"lastPublishedOn\": {\n \"type\": \"string\"\n },\n \"versionDate\": {\n \"type\": \"string\"\n },\n \"origin\": {\n \"type\": \"string\"\n },\n \"originData\": {\n \"type\": \"object\"\n },\n \"versionCreatedBy\": {\n \"type\": \"string\"\n },\n \"me_totalSessionsCount\": {\n \"type\": \"number\"\n },\n \"me_creationSessions\": {\n \"type\": \"number\"\n },\n \"me_creationTimespent\": {\n \"type\": \"number\"\n },\n \"me_totalTimespent\": {\n \"type\": \"number\"\n },\n \"me_totalInteractions\": {\n \"type\": \"number\"\n },\n \"me_averageInteractionsPerMin\": {\n \"type\": \"number\"\n },\n \"me_averageSessionsPerDevice\": {\n \"type\": \"number\"\n },\n \"me_totalDevices\": {\n \"type\": \"number\"\n },\n \"me_averageTimespentPerSession\": {\n \"type\": \"number\"\n },\n \"me_averageRating\": {\n \"type\": \"number\"\n },\n \"me_totalDownloads\": {\n \"type\": \"number\"\n },\n \"me_totalSideloads\": {\n \"type\": \"number\"\n },\n \"me_totalRatings\": {\n \"type\": \"number\"\n },\n \"me_totalComments\": {\n \"type\": \"number\"\n },\n \"me_totalUsage\": {\n \"type\": \"number\"\n },\n \"me_totalLiveContentUsage\": {\n \"type\": \"number\"\n },\n \"me_usageLastWeek\": {\n \"type\": \"number\"\n },\n \"me_deletionsLastWeek\": {\n \"type\": \"number\"\n },\n \"me_lastUsedOn\": {\n \"type\": \"string\"\n },\n \"me_lastRemovedOn\": {\n \"type\": \"string\"\n },\n \"me_hierarchyLevel\": {\n \"type\": \"number\"\n },\n \"me_totalDialcodeAttached\": {\n \"type\": \"number\"\n },\n \"me_totalDialcodeLinkedToContent\": {\n \"type\": \"number\"\n },\n \"me_totalDialcode\": {\n \"type\": \"array\"\n },\n \"flagReasons\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Inappropriate Content\",\n \"Copyright Violation\",\n \"Privacy Violation\",\n \"Other\"\n ]\n }\n },\n \"flaggedBy\": {\n \"type\": \"array\"\n },\n \"flags\": {\n \"type\": \"array\"\n },\n \"lastFlaggedOn\": {\n \"type\": \"string\"\n },\n \"tempData\": {\n \"type\": \"string\"\n },\n \"copyType\": {\n \"type\": \"string\"\n },\n \"pragma\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"external\",\n \"ads\"\n ]\n }\n },\n \"publishChecklist\": {\n \"type\": \"array\"\n },\n \"publishComment\": {\n \"type\": \"string\"\n },\n \"rejectReasons\": {\n \"type\": \"array\"\n },\n \"rejectComment\": {\n \"type\": \"string\"\n },\n \"totalQuestions\": {\n \"type\": \"number\"\n },\n \"totalScore\": {\n \"type\": \"number\"\n },\n \"ownershipType\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"createdBy\",\n \"createdFor\"\n ]\n },\n \"default\": [\"createdBy\"]\n },\n \"reservedDialcodes\": {\n \"type\": \"object\"\n },\n \"dialcodeRequired\": {\n \"type\": \"string\",\n \"enum\": [\n \"Yes\",\n \"No\"\n ],\n \"default\": \"No\"\n },\n \"lockKey\": {\n \"type\": \"string\"\n },\n \"badgeAssociations\": {\n \"type\": \"object\"\n },\n \"framework\": {\n \"type\": \"string\",\n \"default\": \"NCF\"\n },\n \"lastStatusChangedOn\": {\n \"type\": \"string\"\n },\n \"uploadError\": {\n \"type\": \"string\"\n },\n \"appId\": {\n \"type\": \"string\"\n },\n \"s3Key\": {\n \"type\": \"string\"\n },\n \"consumerId\": {\n \"type\": \"string\"\n },\n \"organisation\": {\n \"type\": \"array\"\n },\n \"nodeType\": {\n \"type\": \"string\"\n },\n \"prevState\": {\n \"type\": \"string\"\n },\n \"publishError\": {\n \"type\": \"string\"\n },\n \"publish_type\": {\n \"type\": \"string\"\n },\n \"ownedBy\": {\n \"type\": \"string\"\n },\n \"purpose\": {\n \"type\": \"string\"\n },\n \"toc_url\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"reviewError\": {\n \"type\": \"string\"\n },\n \"mimeTypesCount\": {\n \"type\": \"string\"\n },\n \"contentTypesCount\": {\n \"type\": \"string\"\n },\n \"childNodes\": {\n \"type\": \"array\"\n },\n \"leafNodesCount\": {\n \"type\": \"number\"\n },\n \"depth\": {\n \"type\": \"number\"\n },\n \"SYS_INTERNAL_LAST_UPDATED_ON\": {\n \"type\": \"string\"\n },\n \"assets\": {\n \"type\": \"array\"\n },\n \"version\": {\n \"type\": \"number\",\n \"default\": 2\n },\n \"qrCodeProcessId\": {\n \"type\": \"string\"\n },\n \"migratedUrl\": {\n \"type\": \"string\",\n \"format\": \"url\"\n },\n \"totalCompressedSize\": {\n \"type\": \"number\"\n },\n \"programId\": {\n \"type\": \"string\"\n },\n \"leafNodes\": {\n \"type\": \"array\"\n },\n \"editorVersion\": {\n \"type\": \"number\"\n },\n \"unitIdentifiers\": {\n \"type\": \"array\"\n },\n \"questionCategories\": {\n \"type\": \"array\"\n },\n \"certTemplate\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"subject\" : {\n \"type\": \"array\"\n },\n \"medium\" : {\n \"type\": \"array\"\n },\n \"gradeLevel\" : {\n \"type\": \"array\"\n },\n \"topic\" : {\n \"type\": \"array\"\n },\n \"subDomains\" : {\n \"type\": \"array\"\n },\n \"subjectCodes\" : {\n \"type\": \"array\"\n },\n \"difficultyLevel\" : {\n \"type\": \"string\"\n },\n \"board\" : {\n \"type\": \"string\"\n },\n \"licenseterms\" : {\n \"type\": \"string\"\n },\n \"copyrightYear\" : {\n \"type\": \"number\"\n },\n \"organisationId\" : {\n \"type\": \"string\"\n },\n \"programId\": {\n \"type\": \"string\"\n },\n \"itemSetPreviewUrl\": {\n \"type\": \"string\"\n },\n \"textbook_name\" : {\n \"type\": \"array\"\n },\n \"level1Name\" : {\n \"type\": \"array\"\n },\n \"level1Concept\" : {\n \"type\": \"array\"\n },\n \"level2Name\" : {\n \"type\": \"array\"\n },\n \"level2Concept\" : {\n \"type\": \"array\"\n },\n \"level3Name\" : {\n \"type\": \"array\"\n },\n \"level3Concept\" : {\n \"type\": \"array\"\n },\n \"sourceURL\" : {\n \"type\": \"string\"\n },\n \"me_totalTimeSpentInSec\": {\n \"type\": \"object\"\n },\n \"me_totalPlaySessionCount\": {\n \"type\": \"object\"\n },\n \"me_totalRatingsCount\": {\n \"type\": \"number\"\n },\n \"monitorable\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"progress-report\",\n \"score-report\"\n ]\n }\n },\n \"userConsent\": {\n \"type\": \"string\",\n \"enum\": [\n \"Yes\",\n \"No\"\n ]\n },\n \"trackable\": {\n \"type\": \"object\",\n \"properties\": {\n \"enabled\": {\n \"type\": \"string\",\n \"enum\": [\"Yes\",\"No\"],\n \"default\": \"No\"\n },\n \"autoBatch\": {\n \"type\": \"string\",\n \"enum\": [\"Yes\",\"No\"],\n \"default\": \"No\"\n }\n },\n \"default\": {\n \"enabled\": \"No\",\n \"autoBatch\": \"No\"\n },\n \"additionalProperties\": false\n },\n \"credentials\": {\n \"type\": \"object\",\n \"properties\": {\n \"enabled\": {\n \"type\": \"string\",\n \"enum\": [\"Yes\",\"No\"],\n \"default\": \"No\"\n }\n },\n \"default\": {\n \"enabled\": \"No\"\n },\n \"additionalProperties\": false\n },\n \"processId\": {\n \"type\": \"string\"\n },\n \"primaryCategory\": {\n \"type\": \"string\",\n \"enum\": [\n \"Explanation Content\",\n \"Learning Resource\",\n \"Course\",\n \"Practice Question Set\",\n \"eTextbook\",\n \"Teacher Resource\",\n \"Course Assessment\",\n \"Digital Textbook\",\n \"Content Playlist\",\n \"Template\",\n \"Asset\",\n \"Plugin\",\n \"Lesson Plan Unit\",\n \"Course Unit\",\n \"Textbook Unit\"\n ]\n },\n \"additionalCategories\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Textbook\",\n \"TV Lesson\",\n \"Previous Board Exam Papers\",\n \"Pedagogy Flow\",\n \"Marking Scheme Rubric\",\n \"Lesson Plan\",\n \"Learning Outcome Definition\",\n \"Focus Spot\",\n \"Explanation Video\",\n \"Experiential Resource\",\n \"Curiosity Question Set\",\n \"Concept Map\",\n \"Classroom Teaching Video\"\n ]\n }\n },\n \"prevStatus\": {\n \"type\": \"string\"\n },\n \"cloudStorageKey\": {\n \"type\": \"string\"\n },\n \"batches\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"year\": {\n \"type\": \"string\"\n },\n \"plugins\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"showNotification\": {\n \"type\": \"boolean\"\n },\n \"collectionId\" : {\n \"type\": \"string\"\n },\n \"learningOutcome\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n}"
+ JsonUtils.deserialize(schema, classOf[util.Map[String, AnyRef]])
+ }
+
+ def getContentConfig(): util.Map[String, AnyRef] = {
+ val schema:String = "{\n \"restrictProps\": {\n \"create\" : [\n \"status\", \"dialcodes\"\n ],\n \"copy\" : [\n \"status\"\n ],\n \"update\": []\n },\n \"objectType\": \"Content\",\n \"external\": {\n \"tableName\": \"content_data\",\n \"properties\": {\n \"body\": {\n \"type\": \"blob\"\n },\n \"oldBody\": {\n \"type\": \"blob\"\n },\n \"stageIcons\": {\n \"type\": \"blob\"\n },\n \"screenshots\": {\n \"type\": \"blob\"\n },\n \"last_updated_on\": {\n \"type\": \"timestamp\"\n },\n \"externallink\": {\n \"type\": \"text\"\n }\n },\n \"primaryKey\": [\"content_id\"]\n },\n \"relations\": {\n \"concepts\": {\n \"type\": \"associatedTo\",\n \"direction\": \"out\",\n \"objects\": [\"Concept\"]\n },\n \"questions\": {\n \"type\": \"associatedTo\",\n \"direction\": \"out\",\n \"objects\": [\"AssessmentItem\"]\n },\n \"children\": {\n \"type\": \"hasSequenceMember\",\n \"direction\": \"out\",\n \"objects\": [\"Content\", \"ContentImage\"]\n },\n \"collections\": {\n \"type\": \"hasSequenceMember\",\n \"direction\": \"in\",\n \"objects\": [\"Content\", \"ContentImage\"]\n },\n \"usedByContent\": {\n \"type\": \"associatedTo\",\n \"direction\": \"in\",\n \"objects\": [\"Content\"]\n },\n \"usesContent\": {\n \"type\": \"associatedTo\",\n \"direction\": \"out\",\n \"objects\": [\"Content\"]\n },\n \"itemSets\": {\n \"type\": \"associatedTo\",\n \"direction\": \"out\",\n \"objects\": [\"ItemSet\"]\n }\n },\n \"version\": \"enable\",\n \"versionCheckMode\": \"ON\",\n \"frameworkCategories\": [\"board\",\"medium\",\"subject\",\"gradeLevel\",\"difficultyLevel\",\"topic\", \"subDomains\", \"subjectCodes\"],\n \"edge\": {\n \"properties\": {\n \"license\": \"License\"\n }\n },\n \"copy\": {\n \"scheme\": {\n \"TextBookToCourse\": {\n \"TextBook\": \"Course\",\n \"TextBookUnit\": \"CourseUnit\"\n },\n \"TextBookToLessonPlan\": {\n }\n }\n },\n \"cacheEnabled\": true,\n \"searchProps\": {\n \"status\": [\"Live\"],\n \"softConstraints\": {\n \"medium\": 15,\n \"subject\": 15,\n \"ageGroup\": 1,\n \"gradeLevel\": 7,\n \"board\": 4,\n \"relatedBoards\": 4\n }\n },\n \"schema_restrict_api\": true\n}"
+ JsonUtils.deserialize(schema, classOf[util.Map[String, AnyRef]])
+ }
+
+ def getFrameworkNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("NCF")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Framework")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(Map("name"-> "NCF")))
+ node
+ }
+
+ def getBoardNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("ncf_board_cbse")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Term")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(Map("name"-> "CBSE")))
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventActor.scala
new file mode 100644
index 000000000..9951be514
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventActor.scala
@@ -0,0 +1,212 @@
+package org.sunbird.content.actors
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import java.util
+import scala.collection.JavaConversions._
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future
+
+class TestEventActor extends BaseSpec with MockFactory {
+
+ it should "discard node in draft state should return success" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getValidNodeToDiscard())).anyNumberOfTimes()
+ (graphDB.deleteNode(_: String, _: String, _: Request)).expects(*, *, *).returns(Future(true))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new EventActor()))
+ assert(response.getResponseCode == ResponseCode.OK)
+ assert(response.get("identifier") == "do_12346")
+ assert(response.get("message") == "Draft version of the content with id : do_12346 is discarded")
+
+ }
+
+ it should "publish node in draft state should return success" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDraftNode())).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getDraftNode()))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier", "do_1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("publishContent")
+ val response = callActor(request, Props(new EventActor()))
+ assert(response.getResponseCode == ResponseCode.OK)
+ assert(response.get("identifier") == "do_1234")
+
+ }
+
+ it should "discard node in Live state should return client error" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getInValidNodeToDiscard())).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new EventActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ it should "return client error response for retireContent" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234.img")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234.img")))
+ request.setOperation("retireContent")
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getInValidNodeToDiscard()))
+ val response = callActor(request, Props(new EventActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ it should "return success response for retireContent" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getNode("Content", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node]))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("retireContent")
+ val response = callActor(request, Props(new EventActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'updateContent'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getDraftNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(node))
+ val request = getContentRequest()
+ request.getContext.put("identifier","do_1234")
+ request.putAll(mapAsJavaMap(Map("name" -> "New Content", "code" -> "1234",
+ "startDate" -> "2021-03-04", "endDate" -> "2021-03-04", "startTime" -> "11:00:00Z", "endTime" -> "11:00:00Z",
+ "registrationEndDate" -> "2021-03-04", "eventType" -> "Online", "versionKey" -> "test_123")))
+ request.setOperation("updateContent")
+ val response = callActor(request, Props(new EventActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert("do_1234".equals(response.get("identifier")))
+ assert("test_123".equals(response.get("versionKey")))
+ }
+
+ private def getContentRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Event")
+ put("schemaName", "event")
+ put("X-Channel-Id", "in.ekstep")
+ }
+ })
+ request.setObjectType("Event")
+ request
+ }
+
+ private def getValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("name", "Node To discard")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ private def getInValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("mimeType", "application/pdf")
+ put("status", "Live")
+ put("contentType", "Resource")
+ put("name", "Node To discard")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ private def getNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Event")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("status", "Live")
+ put("name", "Resource_1")
+ put("versionKey", "test_321")
+ put("channel", "in.ekstep")
+ put("code", "Resource_1")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("startTime", "11:00:00Z")
+ put("endTime", "12:00:00Z")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ }
+ })
+ node
+ }
+ private def getDraftNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Event")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("status", "Draft")
+ put("name", "Resource_1")
+ put("versionKey", "test_321")
+ put("channel", "in.ekstep")
+ put("code", "Resource_1")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("startTime", "11:00:00Z")
+ put("endTime", "12:00:00Z")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ }
+ })
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventSetActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventSetActor.scala
new file mode 100644
index 000000000..961c4ae1a
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestEventSetActor.scala
@@ -0,0 +1,392 @@
+package org.sunbird.content.actors
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.JsonUtils
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.common.enums.GraphDACParams
+import org.sunbird.graph.dac.model.{Node, Relation, SearchCriteria}
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import java.util
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.collection.JavaConverters.seqAsJavaListConverter
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future
+
+class TestEventSetActor extends BaseSpec with MockFactory {
+
+ "EventSetActor" should "return failed response for 'unknown' operation" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new EventSetActor()), getContentRequest())
+ }
+
+ it should "validate input before creating event set" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getContentRequest()
+ val eventSet = mapAsJavaMap(Map(
+ "name" -> "New Content", "code" -> "1234",
+ "startDate"-> "2021/01/03", //wrong format
+ "endDate"-> "2021-01-03",
+ "schedule" ->
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03", "endDate" -> "2021-01-03", "startTime" -> "11:00:00Z", "endTime" -> "13:00:00Z"))).asJava)),
+ "onlineProvider" -> "Zoom",
+ "registrationEndDate" -> "2021-02-25",
+ "eventType" -> "Online"))
+ request.putAll(eventSet)
+ assert(true)
+ val response = callActor(request, Props(new EventSetActor()))
+ println("Response: " + JsonUtils.serialize(response))
+ }
+
+ it should "create an eventset and store it in neo4j" in {
+ val eventNode = getEventNode()
+ val eventSetNode = getEventSetNode()
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.addNode _).expects(where { (g: String, n:Node) => {
+ n.getObjectType.equals("Event")
+ }}).returns(Future(eventNode)).once()
+ val loopResult: util.Map[String, Object] = new util.HashMap[String, Object]()
+ loopResult.put(GraphDACParams.loop.name, new java.lang.Boolean(false))
+ (graphDB.checkCyclicLoop _).expects(*, *, *, *).returns(loopResult).anyNumberOfTimes()
+ (graphDB.addNode _).expects(where { (g: String, n:Node) => n.getObjectType.equals("EventSet")}).returns(Future(eventSetNode)).once()
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(new util.ArrayList[Node]() {
+ {
+ add(eventNode)
+ }
+ }))
+ (graphDB.createRelation _).expects(*, *).returns(Future(new Response()))
+ val request = getContentRequest()
+ val eventSet = mapAsJavaMap(Map(
+ "name" -> "New Content", "code" -> "1234",
+ "startDate"-> "2021-01-03", //wrong format
+ "endDate"-> "2021-01-03",
+ "schedule" ->
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03", "endDate" -> "2021-01-03", "startTime" -> "11:00:00Z", "endTime" -> "13:00:00Z"))).asJava)),
+ "onlineProvider" -> "Zoom",
+ "registrationEndDate" -> "2021-02-25",
+ "eventType" -> "Online"))
+ request.putAll(eventSet)
+ request.setOperation("createContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("versionKey") != null)
+ }
+
+ it should "update an eventset and store it in neo4j" in {
+ val eventNode = getEventNode()
+ val eventSetNode = getEventSetCollectionNode()
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.deleteNode(_: String, _: String, _: Request)).expects(*, *, *).returns(Future(true))
+ (graphDB.removeRelation(_: String, _: util.List[util.Map[String, AnyRef]])).expects(*, *).returns(Future(new Response))
+ (graphDB.addNode _).expects(where { (g: String, n:Node) => {
+ n.getObjectType.equals("Event")
+ }}).returns(Future(eventNode))
+ val loopResult: util.Map[String, Object] = new util.HashMap[String, Object]()
+ loopResult.put(GraphDACParams.loop.name, new java.lang.Boolean(false))
+ (graphDB.checkCyclicLoop _).expects(*, *, *, *).returns(loopResult).anyNumberOfTimes()
+ (graphDB.upsertNode _).expects(*, *, *).returns(Future(eventSetNode))
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(new util.ArrayList[Node]() {
+ {
+ add(eventNode)
+ }
+ }))
+ (graphDB.getNodeByUniqueId _).expects(*, *, *, *).returns(Future(eventSetNode)).anyNumberOfTimes()
+ (graphDB.createRelation _).expects(*, *).returns(Future(new Response()))
+ val request = getContentRequest()
+ val eventSet = mapAsJavaMap(Map(
+ "name" -> "New Content", "code" -> "1234",
+ "startDate"-> "2021-01-03", //wrong format
+ "endDate"-> "2021-01-03",
+ "schedule" ->
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03", "endDate" -> "2021-01-03", "startTime" -> "11:00:00Z", "endTime" -> "13:00:00Z"))).asJava)),
+ "onlineProvider" -> "Zoom",
+ "registrationEndDate" -> "2021-02-25",
+ "eventType" -> "Online",
+ "versionKey" -> "test_123"))
+ request.putAll(eventSet)
+ request.setOperation("updateContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("versionKey") != null)
+ }
+
+
+ it should "discard node in draft state should return success" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getValidDraftNode())).twice()
+ (graphDB.deleteNode(_: String, _: String, _: Request)).expects(*, *, *).returns(Future(true))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert(response.getResponseCode == ResponseCode.OK)
+ assert(response.get("identifier") == "do_12346")
+ assert(response.get("message") == "Draft version of the content with id : do_12346 is discarded")
+
+ }
+
+ it should "publish node in draft state should return success" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val eventSetNode = getEventSetCollectionNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(eventSetNode)).anyNumberOfTimes()
+ (graphDB.upsertNode _).expects(*, *, *).returns(Future(eventSetNode)).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("publishContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert(response.getResponseCode == ResponseCode.OK)
+ assert(response.get("identifier") == "do_12345")
+ }
+
+ it should "discard node in Live state should return client error" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getLiveEventSetCollectionNode())).anyNumberOfTimes()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node])).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_12346")))
+ request.setOperation("discardContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ }
+
+ it should "return success response for retireContent" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val node = getEventSetCollectionNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.updateNodes(_: String, _: util.List[String], _: util.HashMap[String, AnyRef])).expects(*, *, *).returns(Future(new util.HashMap[String, Node])).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("retireContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'readContent'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = getNode("EventSet", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "fields" -> "")))
+ request.setOperation("readContent")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ it should "return success response for 'getHierarchy'" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ val node = getNode("EventSet", None)
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node))
+ implicit val ss = mock[StorageService]
+ val request = getContentRequest()
+ request.getContext.put("identifier","do1234")
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234", "fields" -> "")))
+ request.setOperation("getHierarchy")
+ val response = callActor(request, Props(new EventSetActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getContentRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "EventSet")
+ put("schemaName", "eventset")
+ put("X-Channel-Id", "in.ekstep")
+ }
+ })
+ request.setObjectType("EventSet")
+ request
+ }
+
+ private def getValidDraftNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("EventSet")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("status", "Draft")
+ put("contentType", "EventSet")
+ put("name", "Node To discard")
+ }
+ })
+ node
+ }
+
+ private def getInValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("EventSet")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("status", "Live")
+ put("contentType", "EventSet")
+ put("name", "Node To discard")
+ }
+ })
+ node
+ }
+
+ private def getEventNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Event")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("status", "Live")
+ put("name", "Event_1")
+ put("code", "event1")
+ put("versionKey", "1878141")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("startTime", "11:00:00Z")
+ put("endTime", "12:00:00Z")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ }
+ })
+ node
+ }
+
+ private def getEventSetNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12345")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("EventSet")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12345")
+ put("status", "Draft")
+ put("name", "EventSet_1")
+ put("code", "eventset1")
+ put("versionKey", "1878141")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ put("schedule",
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03",
+ "endDate" -> "2021-01-03",
+ "startTime" -> "11:00:00Z",
+ "endTime" -> "13:00:00Z"))).asJava)))
+
+ }
+ })
+ node
+ }
+
+ private def getEventSetCollectionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12345")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("EventSet")
+ val rel: Relation = new Relation()
+ rel.setEndNodeObjectType("Event")
+ rel.setEndNodeId("do_12345.1")
+ rel.setStartNodeId("do_12345")
+ rel.setRelationType("hasSequenceMember")
+ node.setOutRelations(new util.ArrayList[Relation](){
+ add(rel)
+ })
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12345")
+ put("status", "Draft")
+ put("name", "EventSet_1")
+ put("code", "eventset1")
+ put("versionKey", "1878141")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ put("schedule",
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03",
+ "endDate" -> "2021-01-03",
+ "startTime" -> "11:00:00Z",
+ "endTime" -> "13:00:00Z",
+ "status" -> "Draft"))).asJava)))
+
+ }
+ })
+ node
+ }
+
+ private def getLiveEventSetCollectionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12345")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("EventSet")
+ val rel: Relation = new Relation()
+ rel.setEndNodeObjectType("Event")
+ rel.setEndNodeId("do_12345.1")
+ node.setOutRelations(new util.ArrayList[Relation](){
+ add(rel)
+ })
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12345")
+ put("status", "Live")
+ put("name", "EventSet_1")
+ put("code", "eventset1")
+ put("versionKey", "1878141")
+ put("startDate", "2021-02-02")
+ put("endDate", "2021-02-02")
+ put("registrationEndDate", "2021-01-02")
+ put("eventType", "Online")
+ put("schedule",
+ mapAsJavaMap(Map("type" -> "NON_RECURRING",
+ "value" -> List(mapAsJavaMap(Map("startDate" -> "2021-01-03",
+ "endDate" -> "2021-01-03",
+ "startTime" -> "11:00:00Z",
+ "endTime" -> "13:00:00Z",
+ "status" -> "Live"))).asJava)))
+
+ }
+ })
+ node
+ }
+
+
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestLicenseActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestLicenseActor.scala
new file mode 100644
index 000000000..a976c6664
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestLicenseActor.scala
@@ -0,0 +1,137 @@
+package org.sunbird.content.actors
+
+import java.util
+
+import akka.actor.Props
+import org.apache.hadoop.util.StringUtils
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.Node
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class TestLicenseActor extends BaseSpec with MockFactory {
+
+ "LicenseActor" should "return failed response for 'unknown' operation" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ testUnknownOperation(Props(new LicenseActor()), getLicenseRequest())
+ }
+
+ it should "create a licenseNode and store it in neo4j" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB)
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getValidNode()))
+ val request = getLicenseRequest()
+ request.put("name", "do_1234")
+ request.setOperation("createLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert(response.get("identifier") != null)
+ assert(response.get("identifier").equals("do_1234"))
+ }
+
+ it should "return exception for create license without name" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getLicenseRequest()
+ request.setOperation("createLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ assert(StringUtils.equalsIgnoreCase(response.get("messages").asInstanceOf[util.ArrayList[String]].get(0).asInstanceOf[String], "Required Metadata name not set"))
+ }
+
+ it should "return exception for licenseNode with identifier" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val request = getLicenseRequest()
+ request.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ request.setOperation("createLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert(response.getResponseCode == ResponseCode.CLIENT_ERROR)
+ assert(StringUtils.equalsIgnoreCase(response.getParams.getErrmsg, "name will be set as identifier"))
+ }
+
+ it should "return success response for updateLicense" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getValidNode()))
+ implicit val ss = mock[StorageService]
+ val request = getLicenseRequest()
+ request.putAll(mapAsJavaMap(Map("description" -> "test desc")))
+ request.setOperation("updateLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("do_1234"))
+ }
+
+ it should "return success response for readLicense" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(1)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ implicit val ss = mock[StorageService]
+ val request = getLicenseRequest()
+ request.putAll(mapAsJavaMap(Map("fields" -> "")))
+ request.setOperation("readLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(StringUtils.equalsIgnoreCase(response.get("license").asInstanceOf[util.Map[String, AnyRef]].get("identifier").asInstanceOf[String], "do_1234"))
+ assert(StringUtils.equalsIgnoreCase(response.get("license").asInstanceOf[util.Map[String, AnyRef]].get("status").asInstanceOf[String], "Live"))
+ }
+
+ it should "return success response for retireLicense" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).repeated(2)
+ val node = getValidNode()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getValidNode()))
+ implicit val ss = mock[StorageService]
+ val request = getLicenseRequest()
+ request.setOperation("retireLicense")
+ val response = callActor(request, Props(new LicenseActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ assert(response.get("identifier").equals("do_1234"))
+ }
+
+ private def getLicenseRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "License")
+ put("schemaName", "license")
+
+ }
+ })
+ request.setObjectType("License")
+ request
+ }
+
+ private def getValidNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("License")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "License")
+ put("status", "Live")
+ put("name", "do_1234")
+ put("versionKey", "1878141")
+ }
+ })
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/dial/DIALManagerTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/dial/DIALManagerTest.scala
new file mode 100644
index 000000000..fd096d0ad
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/dial/DIALManagerTest.scala
@@ -0,0 +1,364 @@
+package org.sunbird.content.dial
+
+import java.util
+
+import org.scalamock.matchers.Matchers
+import org.scalamock.scalatest.AsyncMockFactory
+import org.scalatest.AsyncFlatSpec
+import org.sunbird.common.{HttpUtil, JsonUtils}
+import org.sunbird.common.dto.{Request, Response}
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException, ResponseCode, ServerException}
+import org.sunbird.graph.dac.model.{Node, SearchCriteria}
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import scala.concurrent.Future
+
+class DIALManagerTest extends AsyncFlatSpec with Matchers with AsyncMockFactory {
+
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ val httpUtil = mock[HttpUtil]
+
+ "getRequestData with list input" should "return request data as list with scala types" in {
+ val reqMap : java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put("content", new util.ArrayList[util.Map[String, AnyRef]](){{
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier","do_1111")
+ put("dialcode", new util.ArrayList[String](){{
+ add("ABC111")
+ add("ABC222")
+ }})
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_2222")
+ add("do_3333")
+ }})
+ put("dialcode", "ABC333")
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_88888")
+ add("do_99999")
+ }})
+ put("dialcode", new util.ArrayList[String]())
+ }})
+ }})
+ }}
+ val request = new Request()
+ request.putAll(reqMap)
+ val result: List[Map[String, List[String]]] = DIALManager.getRequestData(request)
+ assert(null!=result && result.nonEmpty)
+ assert(result.isInstanceOf[List[AnyRef]])
+ assert(result.size==3)
+ assert(result(1).nonEmpty)
+ assert(result(1).get("identifier").get.isInstanceOf[List[String]])
+ assert(result(1).get("dialcode").get.isInstanceOf[List[String]])
+ }
+
+ "getRequestData with map input" should "return request data as list with scala types" in {
+ val reqMap : java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]() {{
+ put("content", new util.HashMap[String, AnyRef](){{
+ put("identifier", "do_123")
+ put("dialcode", new util.ArrayList[String](){{
+ add("ABC123")
+ add("BCD123")
+ }})
+ }})
+ }}
+ val request = new Request()
+ request.putAll(reqMap)
+ val result: List[Map[String, List[String]]] = DIALManager.getRequestData(request)
+ assert(null!=result && result.nonEmpty)
+ assert(result.isInstanceOf[List[AnyRef]])
+ assert(result.size==1)
+ assert(result(0).nonEmpty)
+ assert(result(0).get("identifier").get.isInstanceOf[List[String]])
+ assert(result(0).get("dialcode").get.isInstanceOf[List[String]])
+ }
+
+ "getRequestData with invalid input" should "throw client exception" in {
+ val exception = intercept[ClientException] {
+ DIALManager.getRequestData(new Request())
+ }
+ assert(exception.getMessage == "Invalid Request! Please Provide Valid Request.")
+ }
+
+ "getList with java list input" should "return scala list" in {
+ val input = new util.ArrayList[String](){{
+ add("ABC123")
+ add("")
+ add(" ")
+ add("BCD123")
+ }}
+ val result:List[String] = DIALManager.getList(input)
+ assert(result.nonEmpty)
+ assert(result.size==2)
+ }
+
+ "getList with String input" should "return scala List" in {
+ val input = "do_123"
+ val result:List[String] = DIALManager.getList(input)
+ assert(result.nonEmpty)
+ assert(result.size==1)
+ }
+
+ "getList with empty java list" should "return empty scala List" in {
+ val input = new util.ArrayList[String]()
+ val result:List[String] = DIALManager.getList(input)
+ assert(result.isEmpty)
+ assert(result.size==0)
+ }
+
+ "validateAndGetRequestMap with valid input" should "return the request map" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(getDIALSearchResponse)
+ val input = getRequestData()
+ val result = DIALManager.validateAndGetRequestMap("test", input)
+ assert(result.nonEmpty)
+ assert(result.size==5)
+ assert(result.get("do_88888").get.contains("L4A6W8"))
+ assert(result.get("do_88888").get.contains("D2E1J9"))
+ assert(result.get("do_2222").get.size==1)
+ assert(result.get("do_2222").get.contains("R4X2P2"))
+ }
+
+ "validateReqStructure with valid request" should "not throw any exception" in {
+ DIALManager.validateReqStructure(List("ABC123"), List("do_123"))
+ assert(true)
+ }
+
+ "validateReqStructure with empty contents" should "throw client exception" in {
+ val exception = intercept[ClientException] {
+ DIALManager.validateReqStructure(List("ABC123"), List())
+ }
+ assert(exception.getMessage == "Invalid Request! Please Provide Required Properties In Request.")
+ }
+
+ "validateReqStructure with more than 10 contents" should "throw client exception" in {
+ val exception = intercept[ClientException] {
+ DIALManager.validateReqStructure(List("ABC123"), List("do_111","do_222","do_3333","do_444","do_555","do_1111","do_2222","do_3333","do_4444","do_5555"))
+ }
+ assert(exception.getMessage == "Max Limit For Link Content To DIAL Code In A Request Is 10")
+ }
+
+ "validateDialCodes with valid channel and valid dialcodes" should "return true" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(getDIALSearchResponse)
+ val result = DIALManager.validateDialCodes("test", List("L4A6W8","BCD123","ABC123","PQR123","JKL123"))
+ assert(result)
+ }
+
+ "validateDialCodes with invalid channel and valid dialcodes" should "throw ResourceNotFoundException" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ val resp = new Response
+ resp.put("count",0)
+ resp.put("dialcodes", util.Arrays.asList())
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(resp)
+ val exception = intercept[ResourceNotFoundException] {
+ DIALManager.validateDialCodes("test", List("L4A6W8","BCD123","ABC123","PQR123","JKL123"))
+ }
+ assert(exception.getMessage == "DIAL Code Not Found With Id(s): [L4A6W8, BCD123, ABC123, PQR123, JKL123]")
+ }
+
+ "validateDialCodes with invalid search response" should "throw ServerException" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ val resp = new Response
+ resp.setResponseCode(ResponseCode.SERVER_ERROR)
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(resp)
+ val exception = intercept[ServerException] {
+ DIALManager.validateDialCodes("test", List("L4A6W8","BCD123","ABC123","PQR123","JKL123"))
+ }
+ assert(exception.getMessage == "Something Went Wrong While Processing Your Request. Please Try Again After Sometime!")
+ }
+
+ "link DIAL with valid request for content" should "update the contents successfully" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ (oec.graphService _).expects().returns(graphDB).repeated(4)
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(getDIALSearchResponse)
+ (graphDB.getNodeByUniqueIds(_: String, _: SearchCriteria)).expects(*, *).returns(Future(getNodes()))
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getNode("do_1111")))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getNode("do_1111")))
+ val request = getContentDIALRequest()
+ val resFuture = DIALManager.link(request)
+ resFuture.map(result => {
+ assert(result.getResponseCode.toString=="OK")
+ })
+ }
+
+ "link DIAL with valid request for collections" should "update the collection content successfully" in {
+ (oec.httpUtil _).expects().returns(httpUtil)
+ (httpUtil.post(_: String, _:java.util.Map[String, AnyRef], _:java.util.Map[String, String])).expects(*, *, *).returns(getDIALSearchResponse)
+ val request = getCollectionDIALRequest()
+ val response = DIALManager.link(request)
+ response.map(result => {
+ assert(result.getResponseCode.toString=="OK")
+ })
+ }
+
+ def getDIALSearchResponse():Response = {
+ val resString = "{\n \"id\": \"sunbird.dialcode.search\",\n \"ver\": \"3.0\",\n \"ts\": \"2020-04-21T19:39:14ZZ\",\n \"params\": {\n \"resmsgid\": \"1dfcc25b-6c37-49f8-a6c3-7185063e8752\",\n \"msgid\": null,\n \"err\": null,\n \"status\": \"successful\",\n \"errmsg\": null\n },\n \"responseCode\": \"OK\",\n \"result\": {\n \"dialcodes\": [\n {\n \"dialcode_index\": 7609876,\n \"identifier\": \"N4Z7D5\",\n \"channel\": \"testr01\",\n \"batchcode\": \"testPub0001.20200421T193801\",\n \"publisher\": \"testPub0001\",\n \"generated_on\": \"2020-04-21T19:38:01.603+0000\",\n \"status\": \"Draft\",\n \"objectType\": \"DialCode\"\n },\n {\n \"dialcode_index\": 7610113,\n \"identifier\": \"E8B7Z6\",\n \"channel\": \"testr01\",\n \"batchcode\": \"testPub0001.20200421T193801\",\n \"publisher\": \"testPub0001\",\n \"generated_on\": \"2020-04-21T19:38:01.635+0000\",\n \"status\": \"Draft\",\n \"objectType\": \"DialCode\"\n },\n {\n \"dialcode_index\": 7610117,\n \"identifier\": \"R4X2P2\",\n \"channel\": \"testr01\",\n \"batchcode\": \"testPub0001.20200421T193801\",\n \"publisher\": \"testPub0001\",\n \"generated_on\": \"2020-04-21T19:38:01.637+0000\",\n \"status\": \"Draft\",\n \"objectType\": \"DialCode\"\n },\n {\n \"dialcode_index\": 7610961,\n \"identifier\": \"L4A6W8\",\n \"channel\": \"testr01\",\n \"batchcode\": \"testPub0001.20200421T193801\",\n \"publisher\": \"testPub0001\",\n \"generated_on\": \"2020-04-21T19:38:01.734+0000\",\n \"status\": \"Draft\",\n \"objectType\": \"DialCode\"\n },\n {\n \"dialcode_index\": 7611164,\n \"identifier\": \"D2E1J9\",\n \"channel\": \"testr01\",\n \"batchcode\": \"testPub0001.20200421T193801\",\n \"publisher\": \"testPub0001\",\n \"generated_on\": \"2020-04-21T19:38:01.759+0000\",\n \"status\": \"Draft\",\n \"objectType\": \"DialCode\"\n }\n ],\n \"count\": 5\n }\n}";
+ JsonUtils.deserialize(resString, classOf[Response])
+ }
+
+ def getRequestData(): List[Map[String, List[String]]] = {
+ val reqMap : java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put("content", new util.ArrayList[util.Map[String, AnyRef]](){{
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier","do_1111")
+ put("dialcode", new util.ArrayList[String](){{
+ add("N4Z7D5")
+ add("E8B7Z6")
+ }})
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_2222")
+ add("do_3333")
+ }})
+ put("dialcode", "R4X2P2")
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_88888")
+ add("do_99999")
+ }})
+ put("dialcode", new util.ArrayList[String](){{
+ add("L4A6W8")
+ add("D2E1J9")
+ }})
+ }})
+ }})
+ }}
+ val request = new Request()
+ request.putAll(reqMap)
+ DIALManager.getRequestData(request)
+ }
+
+ def getContentDIALRequest(): Request = {
+ val request = new Request()
+ request.setObjectType("Content")
+ request.setContext(getContext())
+ request.getContext.put("linkType","content")
+ val reqMap : java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put("content", new util.ArrayList[util.Map[String, AnyRef]](){{
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier","do_1111")
+ put("dialcode", new util.ArrayList[String](){{
+ add("N4Z7D5")
+ add("E8B7Z6")
+ add("R4X2P2")
+ add("L4A6W8")
+ add("D2E1J9")
+ }})
+ }})
+ }})
+ }}
+ request.putAll(reqMap)
+ request
+ }
+
+ def getCollectionDIALRequest(): Request = {
+ val request = new Request()
+ request.setObjectType("Content")
+ request.setContext(getContext())
+ request.getContext.put("linkType","collection")
+ request.getContext.put("identifier","do_1111")
+ request.putAll(getRequest())
+ request
+ }
+
+ def getContext():util.Map[String, AnyRef] = {
+ val contextMap: java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put("graph_id", "domain")
+ put("version" , "1.0")
+ put("objectType" , "Content")
+ put("schemaName", "content")
+ put("channel", "test")
+ }}
+ contextMap
+ }
+
+ def getRequest():util.Map[String, AnyRef] = {
+ val reqMap : java.util.Map[String, AnyRef] = new util.HashMap[String, AnyRef](){{
+ put("content", new util.ArrayList[util.Map[String, AnyRef]](){{
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier","do_1111")
+ put("dialcode", new util.ArrayList[String](){{
+ add("N4Z7D5")
+ add("E8B7Z6")
+ }})
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_2222")
+ add("do_3333")
+ }})
+ put("dialcode", "R4X2P2")
+ }})
+ add(new util.HashMap[String, AnyRef](){{
+ put("identifier",new util.ArrayList[String](){{
+ add("do_4444")
+ add("do_5555")
+ }})
+ put("dialcode", new util.ArrayList[String](){{
+ add("L4A6W8")
+ add("D2E1J9")
+ }})
+ }})
+ }})
+ }}
+ reqMap
+ }
+
+ def getNodes(): util.List[Node] = {
+ val result = new util.ArrayList[Node](){{
+ add(getNode("do_1111"))
+ add(getNode("do_2222"))
+ add(getNode("do_3333"))
+ add(getNode("do_4444"))
+ add(getNode("do_5555"))
+ }}
+ result
+ }
+
+ def getNode(identifier: String): Node = {
+ val node = new Node()
+ node.setIdentifier(identifier)
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", identifier)
+ put("name", "Test Content")
+ put("code", "test.resource")
+ put("contentType", "Resource")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("channel", "test")
+ put("versionKey", "1234")
+ put("primaryCategory", "Learning Resource")
+ }
+ })
+ node
+ }
+
+ private def getCategoryDefinitionNode(identifier: String): Node = {
+ val node = new Node()
+ node.setIdentifier(identifier)
+ node.setNodeType("DATA_NODE")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", identifier)
+ put("categoryId", "obj-cat:1234")
+ put("objectType", "ObjectCategoryDefinition")
+ put("name", "Test Category Definition")
+ put("targetObjectType", "Content")
+ put("objectMetadata", "{\"config\":{},\"schema\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}},\"additionalProperties\":false}}}")
+ }
+ })
+ node
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/upload/mgr/UploadManagerTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/upload/mgr/UploadManagerTest.scala
new file mode 100644
index 000000000..44ac88da3
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/upload/mgr/UploadManagerTest.scala
@@ -0,0 +1,24 @@
+package org.sunbird.content.upload.mgr
+
+import java.util
+
+import org.scalatest.{AsyncFlatSpec, Matchers}
+import org.sunbird.graph.dac.model.Node
+
+class UploadManagerTest extends AsyncFlatSpec with Matchers {
+
+ "getUploadResponse with valid node object" should "return response with artifactUrl" in {
+ val node = new Node()
+ node.setIdentifier("do_1234")
+ node.setMetadata(new util.HashMap[String, AnyRef](){{
+ put("artifactUrl", "testurl")
+ put("versionKey",123456.asInstanceOf[AnyRef])
+ }})
+ val response = UploadManager.getUploadResponse(node)
+ val result = response.getResult
+ assert(null != response)
+ assert("OK"==response.getResponseCode.toString)
+ assert(result.size()==5)
+ assert(result.get("artifactUrl").toString.equals("testurl"))
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/CopyManagerTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/CopyManagerTest.scala
new file mode 100644
index 000000000..64e9440d1
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/CopyManagerTest.scala
@@ -0,0 +1,313 @@
+package org.sunbird.content.util
+
+import java.util
+
+import org.apache.commons.collections.MapUtils
+import org.scalamock.scalatest.AsyncMockFactory
+import org.scalatest.{AsyncFlatSpec, Matchers}
+import org.sunbird.cloud.storage.util.JSONUtils
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Property, Request}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.utils.ScalaJsonUtils
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+
+class CopyManagerTest extends AsyncFlatSpec with Matchers with AsyncMockFactory {
+
+ "CopyManager" should "return copied node identifier when content is copied" ignore {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getNode()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDefinitionNode_channel()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDefinitionNode_channel()))
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getCopiedNode()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getCopiedNode()))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getCopiedNode()))
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ CopyManager.copy(getCopyRequest()).map(resp => {
+ assert(resp != null)
+ assert(resp.getResponseCode == ResponseCode.OK)
+ assert(resp.getResult.get("node_id").asInstanceOf[util.HashMap[String, AnyRef]].get("do_1234").asInstanceOf[String] == "do_1234_copy")
+ })
+ }
+
+ it should "return copied node identifier and safe hierarchy in cassandra when collection is copied" in {
+ assert(true)
+ }
+
+ "Required property not sent" should "return client error response for" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getInvalidCopyRequest_2()
+ request.getContext.put("identifier","do_1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ val exception = intercept[ClientException] {
+ CopyManager.validateRequest(request)
+ }
+ exception.getMessage shouldEqual "Please provide valid value for List(createdBy)"
+ }
+
+ "Shallow Copy along with copy scheme" should "return client error response for copy content" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getInvalidCopyRequest_1()
+ request.getContext.put("identifier","do_1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ val exception = intercept[ClientException] {
+ CopyManager.validateRequest(request)
+ }
+ exception.getMessage shouldEqual "Content can not be shallow copied with copy scheme."
+ }
+
+ "Wrong CopyScheme sent" should "return client error response for copy content" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getInvalidCopyRequest_3()
+ request.getContext.put("identifier","do_1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ val exception = intercept[ClientException] {
+ CopyManager.validateRequest(request)
+ }
+ exception.getMessage shouldEqual "Invalid copy scheme, Please provide valid copy scheme"
+ }
+
+ "Valid scheme type update" should "should populate new contentType in metadata" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ implicit val ss = mock[StorageService]
+ val request = getInvalidCopyRequest_3()
+ request.getContext.put("identifier","do_1234")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "do_1234")))
+ val metadata = new util.HashMap[String,Object]()
+ CopyManager.updateToCopySchemeContentType(getValidCopyRequest_1(), "TextBook", metadata)
+ assert(MapUtils.isNotEmpty(metadata))
+ }
+
+
+ private def getNode(): Node = {
+ val node = new Node()
+ node.setGraphId("domain")
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("primaryCategory", "Learning Resource")
+ put("name", "Copy content")
+ put("artifactUrl", "https://ntpstagingall.blob.core.windows.net/ntp-content-staging/content/assets/do_212959046431154176151/hindi3.pdf")
+ put("channel", "in.ekstep")
+ put("code", "xyz")
+ put("versionKey", "1234")
+ }
+ })
+ node
+ }
+
+ private def getCopiedNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234_copy")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setGraphId("domain")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234_copy")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("primaryCategory", "Learning Resource")
+ put("name", "Copy content")
+ put("artifactUrl", "https://ntpstagingall.blob.core.windows.net/ntp-content-staging/content/assets/do_212959046431154176151/hindi3.pdf")
+ put("channel", "in.ekstep")
+ put("code", "xyz")
+ put("versionKey", "1234")
+ }
+ })
+ node
+ }
+
+ private def getCopyRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+
+ }
+ })
+ request.setObjectType("Content")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("createdBy", "EkStep")
+ put("createdFor", new util.ArrayList[String]() {
+ {
+ add("Ekstep")
+ }
+ })
+ put("organisation", new util.ArrayList[String]() {
+ {
+ add("ekstep")
+ }
+ })
+ put("framework", "DevCon-NCERT")
+ }
+ })
+ request
+ }
+
+ private def getInvalidCopyRequest_1(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+ put("copyScheme", "TextBookToCourse")
+ }
+ })
+ request.setObjectType("Content")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("createdBy", "EkStep")
+ put("createdFor", new util.ArrayList[String]() {
+ {
+ add("Ekstep")
+ }
+ })
+ put("organisation", new util.ArrayList[String]() {
+ {
+ add("ekstep")
+ }
+ })
+ put("framework", "DevCon-NCERT")
+ put("copyType", "shallow")
+ }
+ })
+ request
+ }
+
+ private def getInvalidCopyRequest_2(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+ put("copyScheme", "TextBookToCourse")
+ }
+ })
+ request.setObjectType("Content")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("createdFor", new util.ArrayList[String]() {
+ {
+ add("Ekstep")
+ }
+ })
+ put("organisation", new util.ArrayList[String]() {
+ {
+ add("ekstep")
+ }
+ })
+ put("framework", "DevCon-NCERT")
+ }
+ })
+ request
+ }
+
+ private def getInvalidCopyRequest_3(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+ put("copyScheme", "TextBookToCurriculumCourse")
+ }
+ })
+ request.setObjectType("Content")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("createdBy", "EkStep")
+ put("createdFor", new util.ArrayList[String]() {
+ {
+ add("Ekstep")
+ }
+ })
+ put("organisation", new util.ArrayList[String]() {
+ {
+ add("ekstep")
+ }
+ })
+ put("framework", "DevCon-NCERT")
+ }
+ })
+ request
+ }
+
+ private def getValidCopyRequest_1(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+ put("copyScheme", "TextBookToCourse")
+ }
+ })
+ request.setObjectType("Content")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("createdBy", "EkStep")
+ put("createdFor", new util.ArrayList[String]() {
+ {
+ add("Ekstep")
+ }
+ })
+ put("organisation", new util.ArrayList[String]() {
+ {
+ add("ekstep")
+ }
+ })
+ put("framework", "DevCon-NCERT")
+ }
+ })
+ request
+ }
+
+ def getDefinitionNode_channel(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:learning-resource_content_in.ekstep")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String,AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:learning-resource\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+
+ def getDefinitionNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:learning-resource_content_all")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Content")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String,AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:learning-resource\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/DiscardManagerTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/DiscardManagerTest.scala
new file mode 100644
index 000000000..45f19480c
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/DiscardManagerTest.scala
@@ -0,0 +1,63 @@
+package org.sunbird.content.util
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import akka.actor.Props
+import org.apache.commons.lang3.BooleanUtils
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ClientException
+import org.sunbird.content.actors.BaseSpec
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class DiscardManagerTest extends BaseSpec with MockFactory {
+
+ it should "discard node in Live state should return client error" in {
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+ val request = getContentRequest()
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "")))
+ request.setOperation("discardContent")
+ val exception = intercept[ClientException] {
+ DiscardManager.validateRequest(request)
+ }
+ exception.getMessage shouldEqual "Please provide valid content identifier"
+ }
+
+ private def getContentRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Content")
+ put("schemaName", "content")
+
+ }
+ })
+ request.setObjectType("Content")
+ request
+ }
+
+ private def getInValidNodeToDiscard(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_12346")
+ node.setNodeType("DATA_NODE")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_12346")
+ put("mimeType", "application/pdf")
+ put("status", "Live")
+ put("contentType", "Resource")
+ put("name", "Node To discard")
+ }
+ })
+ node
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/FlagManagerTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/FlagManagerTest.scala
new file mode 100644
index 000000000..06344afbb
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/FlagManagerTest.scala
@@ -0,0 +1,29 @@
+package org.sunbird.content.util
+
+import java.util
+
+import org.scalamock.scalatest.MockFactory
+import org.scalatest.{FlatSpec, Matchers}
+class FlagManagerTest extends FlatSpec with Matchers with MockFactory {
+
+ "addFlagReasons with metadata metadata without flagReasons" should "return flaggedList with only list with request flagReasons value" in {
+ val requestFlagReasons = java.util.Arrays.asList("Not a valid content")
+ val metadata = new util.HashMap[String, AnyRef]()
+ val flaggedByList = FlagManager.addDataIntoList(requestFlagReasons, metadata, "flagReasons")
+ assert(flaggedByList.size()==1)
+ assert(flaggedByList.containsAll(java.util.Arrays.asList("Not a valid content")))
+ }
+
+ "addFlagReasons with metadata with flagReasons as list of string" should "return flaggedList with list of requestFlagReasons and metadata flagReasons value" in {
+ val requestFlagReasons = new java.util.ArrayList[String]
+ requestFlagReasons.add("Not a valid content")
+ val flagReasons = new java.util.ArrayList[String]
+ flagReasons.add("Others")
+ val metadata = new util.HashMap[String, AnyRef](){{
+ put("flagReasons", flagReasons)
+ }}
+ val flaggedByList = FlagManager.addDataIntoList(requestFlagReasons, metadata, "flagReasons")
+ assert(flaggedByList.size()==2)
+ assert(flaggedByList.containsAll(java.util.Arrays.asList("Not a valid content", "Others")))
+ }
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/RequestUtilTest.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/RequestUtilTest.scala
new file mode 100644
index 000000000..b3cff3429
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/RequestUtilTest.scala
@@ -0,0 +1,34 @@
+package org.sunbird.content.util
+
+import java.util
+
+import org.scalamock.scalatest.AsyncMockFactory
+import org.scalatest.{FlatSpec, Matchers}
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ClientException
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.util.RequestUtil
+
+
+class RequestUtilTest extends FlatSpec with Matchers with AsyncMockFactory {
+
+
+ it should "throw clientException for invalid request" in {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val exception = intercept[ClientException] {
+ val context = new util.HashMap[String, AnyRef](){{
+ put("graphId", "domain")
+ put("version", "1.0")
+ put("schemaName", "content")
+ put("objectType", "Content")
+ }}
+ val request = new Request()
+ request.setContext(context)
+ request.setOperation("create")
+ request.put("status", "Live")
+ RequestUtil.restrictProperties(request)
+ }
+ exception.getErrCode shouldEqual "ERROR_RESTRICTED_PROP"
+ }
+
+}
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAcceptFlagManager.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAcceptFlagManager.scala
new file mode 100644
index 000000000..eeef3f2d2
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAcceptFlagManager.scala
@@ -0,0 +1,60 @@
+package org.sunbird.content.util
+
+import java.util
+
+import akka.actor.Props
+import org.scalamock.scalatest.MockFactory
+import org.sunbird.cloudstore.StorageService
+import org.sunbird.common.dto.{Property, Request, Response}
+import org.sunbird.content.actors.{BaseSpec, ContentActor}
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.Node
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future
+
+class TestAcceptFlagManager extends BaseSpec with MockFactory {
+
+ it should "return success response for acceptFlag for Resource" in {
+ implicit val ss = mock[StorageService]
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ val nodeMetaData = new util.HashMap[String, AnyRef]() {{
+ put("name", "Domain")
+ put("code", "domain")
+ put("status", "Flagged")
+ put("identifier", "domain")
+ put("versionKey", "1234")
+ put("contentType", "Resource")
+ put("channel", "Test")
+ put("mimeType", "application/pdf")
+ put("primaryCategory", "Learning Resource")
+ }}
+ val node = getNode("Content", Option(nodeMetaData))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.upsertNode(_:String, _: Node, _: Request)).expects(*, *, *).returns(Future(node)).anyNumberOfTimes()
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234")))).anyNumberOfTimes()
+ (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(new Response()))
+ val request = getRequest()
+ request.getContext.put("identifier","domain")
+ request.getRequest.putAll(mapAsJavaMap(Map("identifier" -> "domain")))
+ request.setOperation("acceptFlag")
+ val response = callActor(request, Props(new ContentActor()))
+ assert("successful".equals(response.getParams.getStatus))
+ }
+
+ private def getRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "content")
+ }
+ })
+ request
+ }
+}
\ No newline at end of file
diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAssetManager.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAssetManager.scala
new file mode 100644
index 000000000..62dc10c11
--- /dev/null
+++ b/content-api/content-actors/src/test/scala/org/sunbird/content/util/TestAssetManager.scala
@@ -0,0 +1,113 @@
+package org.sunbird.content.util
+
+import java.util
+
+import org.scalamock.scalatest.AsyncMockFactory
+import org.scalatest.{AsyncFlatSpec, Matchers}
+import org.sunbird.common.dto.{Property, Request}
+import org.sunbird.common.exception.ResponseCode
+import org.sunbird.graph.{GraphService, OntologyEngineContext}
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.utils.ScalaJsonUtils
+
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.concurrent.Future
+
+class TestAssetManager extends AsyncFlatSpec with Matchers with AsyncMockFactory {
+ "AssetCopyManager" should "return copied node identifier when asset is copied" ignore {
+ implicit val oec: OntologyEngineContext = mock[OntologyEngineContext]
+ val graphDB = mock[GraphService]
+ (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes()
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getNode()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDefinitionNode_channel()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getDefinitionNode_channel()))
+ (graphDB.addNode(_: String, _: Node)).expects(*, *).returns(Future(getCopiedNode()))
+ (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(getCopiedNode()))
+ (graphDB.upsertNode(_: String, _: Node, _: Request)).expects(*, *, *).returns(Future(getCopiedNode()))
+ (graphDB.getNodeProperty(_: String, _: String, _: String)).expects(*, *, *).returns(Future(new Property("versionKey", new org.neo4j.driver.internal.value.StringValue("1234"))))
+ AssetCopyManager.copy(getCopyRequest()).map(resp => {
+ assert(resp != null)
+ assert(resp.getResponseCode == ResponseCode.OK)
+ assert(resp.getResult.get("node_id").asInstanceOf[util.HashMap[String, AnyRef]].get("do_1234").asInstanceOf[String] == "do_1234_copy")
+ })
+ }
+
+ private def getCopyRequest(): Request = {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("objectType", "Asset")
+ put("schemaName", "content")
+
+ }
+ })
+ request.setObjectType("Asset")
+ request.putAll(new util.HashMap[String, AnyRef]() {
+ {
+ put("name", "test")
+ }
+ })
+ request
+ }
+
+ private def getNode(): Node = {
+ val node = new Node()
+ node.setGraphId("domain")
+ node.setIdentifier("do_1234")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Asset")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("primaryCategory", "Learning Resource")
+ put("name", "Copy content")
+ put("artifactUrl", "https://ntpstagingall.blob.core.windows.net/ntp-content-staging/content/assets/do_212959046431154176151/hindi3.pdf")
+ put("channel", "in.ekstep")
+ put("code", "xyz")
+ put("versionKey", "1234")
+ }
+ })
+ node
+ }
+
+
+ def getDefinitionNode_channel(): Node = {
+ val node = new Node()
+ node.setIdentifier("obj-cat:learning-resource_content_in.ekstep")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Asset")
+ node.setGraphId("domain")
+ node.setMetadata(mapAsJavaMap(
+ ScalaJsonUtils.deserialize[Map[String, AnyRef]]("{\n \"objectCategoryDefinition\": {\n \"name\": \"Learning Resource\",\n \"description\": \"Content Playlist\",\n \"categoryId\": \"obj-cat:learning-resource\",\n \"targetObjectType\": \"Content\",\n \"objectMetadata\": {\n \"config\": {},\n \"schema\": {\n \"required\": [\n \"author\",\n \"copyright\",\n \"audience\"\n ],\n \"properties\": {\n \"audience\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"Student\",\n \"Teacher\"\n ]\n },\n \"default\": [\n \"Student\"\n ]\n },\n \"mimeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"application/pdf\"\n ]\n }\n }\n }\n }\n }\n }")))
+ node
+ }
+
+ private def getCopiedNode(): Node = {
+ val node = new Node()
+ node.setIdentifier("do_1234_copy")
+ node.setNodeType("DATA_NODE")
+ node.setObjectType("Asset")
+ node.setGraphId("domain")
+ node.setMetadata(new util.HashMap[String, AnyRef]() {
+ {
+ put("identifier", "do_1234_copy")
+ put("mimeType", "application/pdf")
+ put("status", "Draft")
+ put("contentType", "Resource")
+ put("primaryCategory", "Learning Resource")
+ put("name", "Copy content")
+ put("artifactUrl", "https://ntpstagingall.blob.core.windows.net/ntp-content-staging/content/assets/do_212959046431154176151/hindi3.pdf")
+ put("channel", "in.ekstep")
+ put("code", "xyz")
+ put("versionKey", "1234")
+ }
+ })
+ node
+ }
+
+}
diff --git a/learning-api/content-service/.gitignore b/content-api/content-service/.gitignore
similarity index 100%
rename from learning-api/content-service/.gitignore
rename to content-api/content-service/.gitignore
diff --git a/content-api/content-service/app/controllers/BaseController.scala b/content-api/content-service/app/controllers/BaseController.scala
new file mode 100644
index 000000000..4374015b5
--- /dev/null
+++ b/content-api/content-service/app/controllers/BaseController.scala
@@ -0,0 +1,211 @@
+package controllers
+
+
+import java.io.File
+import java.util
+import java.util.UUID
+
+import akka.actor.ActorRef
+import akka.pattern.Patterns
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.{DateUtils, Platform}
+import org.sunbird.common.dto.{Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ResponseCode}
+import play.api.mvc._
+import utils.{Constants, JavaJsonUtils}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext, Future}
+
+abstract class BaseController(protected val cc: ControllerComponents)(implicit exec: ExecutionContext) extends AbstractController(cc) {
+ val categoryMap: java.util.Map[String, AnyRef] = Platform.getAnyRef("contentTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val categoryMapForMimeType: java.util.Map[String, AnyRef] = Platform.getAnyRef("mimeTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val categoryMapForResourceType: java.util.Map[String, AnyRef] = Platform.getAnyRef("resourceTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val mimeTypesToCheck = List("application/vnd.ekstep.h5p-archive", "application/vnd.ekstep.html-archive", "application/vnd.android.package-archive",
+ "video/webm", "video/x-youtube", "video/mp4")
+
+ def requestBody()(implicit request: Request[AnyContent]) = {
+ val body = request.body.asJson.getOrElse("{}").toString
+ JavaJsonUtils.deserialize[java.util.Map[String, Object]](body).getOrDefault("request", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ }
+
+ def requestFormData(identifier: String)(implicit request: Request[AnyContent]) = {
+ val reqMap = new util.HashMap[String, AnyRef]()
+ if(!request.body.asMultipartFormData.isEmpty) {
+ val multipartData = request.body.asMultipartFormData.get
+ if (null != multipartData.asFormUrlEncoded && !multipartData.asFormUrlEncoded.isEmpty) {
+ if(multipartData.asFormUrlEncoded.getOrElse("fileUrl",Seq()).length > 0){
+ val fileUrl: String = multipartData.asFormUrlEncoded.getOrElse("fileUrl",Seq()).head
+ if (StringUtils.isNotBlank(fileUrl))
+ reqMap.put("fileUrl", fileUrl)
+ }
+ if(multipartData.asFormUrlEncoded.getOrElse("filePath",Seq()).length > 0){
+ val filePath: String = multipartData.asFormUrlEncoded.getOrElse("filePath",Seq()).head
+ if (StringUtils.isNotBlank(filePath))
+ reqMap.put("filePath", filePath)
+ }
+ }
+ if (null != multipartData.files && !multipartData.files.isEmpty) {
+ val file: File = new File("/tmp" + File.separator + identifier + "_" + System.currentTimeMillis + "_"+ request.body.asMultipartFormData.get.files.head.filename)
+ val copiedFile: File = multipartData.files.head.ref.copyTo(file, false).toFile
+ reqMap.put("file", copiedFile)
+ }
+ }
+ if(StringUtils.isNotBlank(reqMap.getOrDefault("fileUrl", "").asInstanceOf[String]) || null != reqMap.get("file").asInstanceOf[File]){
+ reqMap
+ } else {
+ throw new ClientException("ERR_INVALID_DATA", "Please Provide Valid File Or File Url!")
+ }
+ }
+
+ def commonHeaders(ignoreHeaders: Option[List[String]] = Option(List()))(implicit request: Request[AnyContent]): java.util.Map[String, Object] = {
+ val customHeaders = Map("x-channel-id" -> "channel", "X-Consumer-ID" -> "consumerId", "X-App-Id" -> "appId").filterKeys(key => !ignoreHeaders.getOrElse(List()).contains(key))
+ customHeaders.map(ch => {
+ val value = request.headers.get(ch._1)
+ if (value.isDefined && !value.isEmpty) {
+ collection.mutable.HashMap[String, Object](ch._2 -> value.get).asJava
+ } else {
+ collection.mutable.HashMap[String, Object]().asJava
+ }
+ }).reduce((a, b) => {
+ a.putAll(b)
+ return a
+ })
+ }
+
+ def getRequest(input: java.util.Map[String, AnyRef], context: java.util.Map[String, AnyRef], operation: String, categoryMapping: Boolean = false): org.sunbird.common.dto.Request = {
+ //Todo mapping and reverse mapping
+ if (categoryMapping) setContentAndCategoryTypes(input)
+ new org.sunbird.common.dto.Request(context, input, operation, null);
+ }
+
+ def getResult(apiId: String, actor: ActorRef, request: org.sunbird.common.dto.Request, categoryMapping: Boolean = false, version: String = "3.0") : Future[Result] = {
+ val future = Patterns.ask(actor, request, 30000) recoverWith {case e: Exception => Future(ResponseHandler.getErrorResponse(e))}
+ future.map(f => {
+ val result: Response = f.asInstanceOf[Response]
+ result.setId(apiId)
+ result.setVer(version)
+ setResponseEnvelope(result)
+ //TODO Mapping for backward compatibility
+ if (categoryMapping && result.getResponseCode == ResponseCode.OK) {
+ setContentAndCategoryTypes(result.getResult.getOrDefault("content", new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]])
+ val objectType = result.getResult.getOrDefault("content", new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]].getOrDefault("objectType", "Content").asInstanceOf[String]
+ setObjectTypeForRead(objectType, result.getResult.getOrDefault("content", new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]])
+ }
+ val response: String = JavaJsonUtils.serialize(result);
+ result.getResponseCode match {
+ case ResponseCode.OK => Ok(response).as("application/json")
+ case ResponseCode.CLIENT_ERROR => BadRequest(response).as("application/json")
+ case ResponseCode.RESOURCE_NOT_FOUND => NotFound(response).as("application/json")
+ case ResponseCode.PARTIAL_SUCCESS => MultiStatus(response).as("application/json")
+ case _ => play.api.mvc.Results.InternalServerError(response).as("application/json")
+ }
+ })
+ }
+
+ def setResponseEnvelope(response: Response) = {
+ response.setTs(DateUtils.formatCurrentDate("yyyy-MM-dd'T'HH:mm:ss'Z'XXX"))
+ response.getParams.setResmsgid(UUID.randomUUID().toString)
+ }
+
+ def setRequestContext(request: org.sunbird.common.dto.Request, version: String, objectType: String, schemaName: String): Unit = {
+ val mimeType = request.getRequest.getOrDefault("mimeType", "").asInstanceOf[String]
+ val contentType = request.getRequest.getOrDefault("contentType", "").asInstanceOf[String]
+ val primaryCategory = request.getRequest.getOrDefault("primaryCategory", "").asInstanceOf[String]
+ val contextMap: java.util.Map[String, AnyRef] = if (StringUtils.isNotBlank(mimeType) && StringUtils.equalsIgnoreCase(mimeType, Constants.COLLECTION_MIME_TYPE)) {
+ request.setObjectType(Constants.COLLECTION_OBJECT_TYPE)
+ new java.util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", Constants.COLLECTION_VERSION)
+ put("objectType", Constants.COLLECTION_OBJECT_TYPE)
+ put("schemaName", Constants.COLLECTION_SCHEMA_NAME)
+ }
+ }
+ } else if ((StringUtils.isNotBlank(contentType) && StringUtils.equalsIgnoreCase(contentType, Constants.ASSET_CONTENT_TYPE))
+ || (StringUtils.isNotBlank(primaryCategory) && StringUtils.equalsIgnoreCase(primaryCategory, Constants.ASSET_CONTENT_TYPE))) {
+ request.setObjectType(Constants.ASSET_OBJECT_TYPE)
+ new java.util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", Constants.ASSET_VERSION)
+ put("objectType", Constants.ASSET_OBJECT_TYPE)
+ put("schemaName", Constants.ASSET_SCHEMA_NAME)
+ }
+ }
+ } else {
+ request.setObjectType(objectType)
+ new java.util.HashMap[String, AnyRef]() {
+ {
+ put("graph_id", "domain")
+ put("version", version)
+ put("objectType", objectType)
+ put("schemaName", schemaName)
+ }
+ }
+ }
+ if(StringUtils.isNotBlank(request.getContext.getOrDefault("channel", "").asInstanceOf[String]))
+ contextMap.put("channel", request.getContext.get("channel").asInstanceOf[String])
+ request.setContext(contextMap)
+ }
+
+ private def setContentAndCategoryTypes(input: java.util.Map[String, AnyRef]): Unit = {
+ val contentType = input.get("contentType").asInstanceOf[String]
+ val primaryCategory = input.get("primaryCategory").asInstanceOf[String]
+ val (updatedContentType, updatedPrimaryCategory): (String, String) = (contentType, primaryCategory) match {
+ case (x: String, y: String) => (x, y)
+ case ("Resource", y) => (contentType, getCategoryForResource(input.getOrDefault("mimeType", "").asInstanceOf[String],
+ input.getOrDefault("resourceType", "").asInstanceOf[String]))
+ case (x: String, y) => (x, getPrimeryCategory(x))
+ case (x, y: String) => (getContentType(y), y)
+ case _ => (contentType, primaryCategory)
+ }
+ input.put("contentType", if (StringUtils.isBlank(updatedContentType)) "Resource" else updatedContentType)
+ input.put("primaryCategory", updatedPrimaryCategory)
+ }
+
+ private def getPrimeryCategory(contentType: String): String ={
+ val primaryCategory = categoryMap.get(contentType)
+ if(primaryCategory.isInstanceOf[String])
+ primaryCategory.asInstanceOf[String]
+ else
+ primaryCategory.asInstanceOf[util.List[String]].asScala.headOption.getOrElse("Learning Resource")
+
+ }
+
+ private def getContentType(primaryCategory: String): String ={
+ categoryMap.asScala.filter(entry => (entry._2 match{
+ case xs: util.List[_] => xs.asInstanceOf[util.List[String]].contains(primaryCategory)
+ case _ => StringUtils.equalsIgnoreCase(entry._2.asInstanceOf[String], primaryCategory)
+
+ })).keys.headOption.getOrElse("Resource")
+ }
+
+ private def getCategoryForResource(mimeType: String, resourceType: String): String = (mimeType, resourceType) match {
+ case ("", "") => "Learning Resource"
+ case (x: String, "") => categoryMapForMimeType.get(x).asInstanceOf[util.List[String]].asScala.headOption.getOrElse("Learning Resource")
+ case (x: String, y: String) => if (mimeTypesToCheck.contains(x)) categoryMapForMimeType.get(x).asInstanceOf[util.List[String]].asScala.headOption.getOrElse("Learning Resource") else categoryMapForResourceType.getOrDefault(y, "Learning Resource").asInstanceOf[String]
+ case _ => "Learning Resource"
+ }
+
+ private def setObjectTypeForRead(objectType: String, result: java.util.Map[String, AnyRef]): Unit = {
+ result.put("objectType", "Content")
+ }
+
+ def validatePrimaryCategory(input: java.util.Map[String, AnyRef]): Boolean = StringUtils.isNotBlank(input.getOrDefault("primaryCategory", "").asInstanceOf[String])
+
+ def validateContentType(input: java.util.Map[String, AnyRef]): Boolean = StringUtils.isNotBlank(input.getOrDefault("contentType", "").asInstanceOf[String])
+
+
+ def getErrorResponse(apiId: String, version: String, errCode: String, errMessage: String): Future[Result] = {
+ val result = ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, errCode, errMessage)
+ result.setId(apiId)
+ result.setVer(version)
+ setResponseEnvelope(result)
+ Future(BadRequest(JavaJsonUtils.serialize(result)).as("application/json"))
+ }
+
+}
diff --git a/content-api/content-service/app/controllers/HealthController.scala b/content-api/content-service/app/controllers/HealthController.scala
new file mode 100644
index 000000000..d278b7681
--- /dev/null
+++ b/content-api/content-service/app/controllers/HealthController.scala
@@ -0,0 +1,31 @@
+package controllers
+
+import akka.actor.{ActorRef, ActorSystem}
+import handlers.SignalHandler
+import javax.inject._
+import org.sunbird.common.JsonUtils
+import org.sunbird.common.dto.ResponseHandler
+import play.api.mvc._
+import utils.{ActorNames, ApiId}
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class HealthController @Inject()(@Named(ActorNames.HEALTH_ACTOR) healthActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem, signalHandler: SignalHandler)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ def health() = Action.async { implicit request =>
+ if (signalHandler.isShuttingDown) {
+ Future { ServiceUnavailable }
+ } else {
+ getResult(ApiId.APPLICATION_HEALTH, healthActor, new org.sunbird.common.dto.Request())
+ }
+ }
+
+ def serviceHealth() = Action.async { implicit request =>
+ if (signalHandler.isShuttingDown)
+ Future { ServiceUnavailable }
+ else {
+ val response = ResponseHandler.OK().setId(ApiId.APPLICATION_SERVICE_HEALTH).put("healthy", true)
+ Future { Ok(JsonUtils.serialize(response)).as("application/json") }
+ }
+ }
+}
diff --git a/content-api/content-service/app/controllers/v3/CategoryController.scala b/content-api/content-service/app/controllers/v3/CategoryController.scala
new file mode 100644
index 000000000..59bcce644
--- /dev/null
+++ b/content-api/content-service/app/controllers/v3/CategoryController.scala
@@ -0,0 +1,61 @@
+package controllers.v3
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import org.sunbird.content.util.CategoryConstants
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.ExecutionContext
+
+@Singleton
+class CategoryController @Inject()(@Named(ActorNames.CATEGORY_ACTOR) categoryActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "Category"
+ val schemaName: String = "category"
+ val version = "1.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val category = body.getOrDefault("category", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ category.putAll(headers)
+ val categoryRequest = getRequest(category, headers, CategoryConstants.CREATE_CATEGORY)
+ setRequestContext(categoryRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_CATEGORY, categoryActor, categoryRequest)
+ }
+
+ def read(identifier: String, fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val category = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ category.putAll(headers)
+ category.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse("")).asJava)
+ val categoryRequest = getRequest(category, headers, CategoryConstants.READ_CATEGORY)
+ setRequestContext(categoryRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_CATEGORY, categoryActor, categoryRequest)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val category = body.getOrDefault("category", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ category.putAll(headers)
+ val categoryRequest = getRequest(category, headers, CategoryConstants.UPDATE_CATEGORY)
+ setRequestContext(categoryRequest, version, objectType, schemaName)
+ categoryRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_CATEGORY, categoryActor, categoryRequest)
+ }
+
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val category = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ category.putAll(headers)
+ val categoryRequest = getRequest(category, headers, CategoryConstants.RETIRE_CATEGORY)
+ setRequestContext(categoryRequest, version, objectType, schemaName)
+ categoryRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.RETIRE_CATEGORY, categoryActor, categoryRequest)
+ }
+}
diff --git a/content-api/content-service/app/controllers/v3/ChannelController.scala b/content-api/content-service/app/controllers/v3/ChannelController.scala
new file mode 100644
index 000000000..c798ca511
--- /dev/null
+++ b/content-api/content-service/app/controllers/v3/ChannelController.scala
@@ -0,0 +1,60 @@
+package controllers.v3
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import scala.concurrent.{ExecutionContext}
+
+@Singleton
+class ChannelController @Inject()(@Named(ActorNames.CHANNEL_ACTOR) channelActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "Channel"
+ val schemaName: String = "channel"
+ val version = "1.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders(Option(List("x-channel-id")))
+ val body = requestBody()
+ val channel = body.getOrDefault("channel", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ channel.putAll(headers)
+ val channelRequest = getRequest(channel, headers, "createChannel")
+ setRequestContext(channelRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_CHANNEL, channelActor, channelRequest)
+ }
+
+ def read(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val channel = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ channel.put("identifier", identifier)
+ channel.putAll(headers)
+ val readRequest = getRequest(channel, headers, "readChannel")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_CHANNEL, channelActor, readRequest)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders(Option(List("x-channel-id")))
+ val body = requestBody()
+ val channel = body.getOrDefault("channel", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ channel.putAll(headers)
+ val channelRequest = getRequest(channel, headers, "updateChannel")
+ setRequestContext(channelRequest, version, objectType, schemaName)
+ channelRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.UPDATE_CHANNEL, channelActor, channelRequest)
+ }
+
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders(Option(List("x-channel-id")))
+ val channel = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ channel.putAll(headers)
+ val channelRequest = getRequest(channel, headers, "retireChannel")
+ setRequestContext(channelRequest, version, objectType, schemaName)
+ channelRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.RETIRE_CHANNEL, channelActor, channelRequest)
+ }
+}
+
diff --git a/content-api/content-service/app/controllers/v3/ContentController.scala b/content-api/content-service/app/controllers/v3/ContentController.scala
new file mode 100644
index 000000000..6a7abdd35
--- /dev/null
+++ b/content-api/content-service/app/controllers/v3/ContentController.scala
@@ -0,0 +1,269 @@
+package controllers.v3
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import org.sunbird.models.UploadParams
+import org.sunbird.common.dto.ResponseHandler
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId, JavaJsonUtils}
+
+import scala.collection.JavaConverters._
+
+import scala.concurrent.{ExecutionContext, Future}
+
+@Singleton
+class ContentController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor: ActorRef, @Named(ActorNames.COLLECTION_ACTOR) collectionActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "Content"
+ val schemaName: String = "content"
+ val version = "1.0"
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "createContent", true)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_CONTENT, contentActor, contentRequest)
+
+ }
+
+ /**
+ * This Api end point takes 3 parameters
+ * Content Identifier the unique identifier of a content
+ * Mode in which the content can be viewed (default read or edit)
+ * Fields are metadata that should be returned to visualize
+ *
+ * @param identifier
+ * @param mode
+ * @param fields
+ * @return
+ */
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_CONTENT, contentActor, readRequest, true)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.UPDATE_CONTENT, contentActor, contentRequest)
+ }
+
+ def addHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "addHierarchy")
+ contentRequest.put("mode", "edit");
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.ADD_HIERARCHY, collectionActor, contentRequest)
+ }
+
+ def removeHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "removeHierarchy")
+ contentRequest.put("mode", "edit");
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.REMOVE_HIERARCHY, collectionActor, contentRequest)
+ }
+
+ def updateHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val data = body.getOrDefault("data", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ data.putAll(headers)
+ val contentRequest = getRequest(data, headers, "updateHierarchy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.UPDATE_HIERARCHY, collectionActor, contentRequest)
+ }
+
+ def getHierarchy(identifier: String, mode: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("rootId" -> identifier, "mode" -> mode.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, null)
+ getResult(ApiId.GET_HIERARCHY, collectionActor, readRequest, true)
+ }
+
+ def getBookmarkHierarchy(identifier: String, bookmarkId: String, mode: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("rootId" -> identifier, "bookmarkId" -> bookmarkId, "mode" -> mode.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, null)
+ getResult(ApiId.GET_HIERARCHY, collectionActor, readRequest, true)
+ }
+
+ def flag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val contentRequest = getRequest(content, headers, "flagContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.FlAG_CONTENT, contentActor, contentRequest)
+ }
+
+ def acceptFlag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val acceptRequest = getRequest(content, headers, "acceptFlag")
+ setRequestContext(acceptRequest, version, objectType, schemaName)
+ getResult(ApiId.ACCEPT_FLAG, contentActor, acceptRequest)
+ }
+
+ def rejectFlag(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def bundle() = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def publish(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def review(identfier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def discard(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val discardRequest = getRequest(content, headers, "discardContent")
+ setRequestContext(discardRequest, version, objectType, schemaName)
+ getResult(ApiId.DISCARD_CONTENT, contentActor, discardRequest)
+ }
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.put("identifier", identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "retireContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.RETIRE_CONTENT, contentActor, contentRequest)
+ }
+
+ def linkDialCode() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "linkDIALCode")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("linkType", "content")
+ getResult(ApiId.LINK_DIAL_CONTENT, contentActor, contentRequest)
+ }
+
+ def collectionLinkDialCode(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "linkDIALCode")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("linkType", "collection")
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.LINK_DIAL_COLLECTION, contentActor, contentRequest)
+ }
+
+ def reserveDialCode(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def releaseDialcodes(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def rejectContent(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def publishUnlisted(identifier: String) = Action.async { implicit request =>
+ val result = ResponseHandler.OK()
+ val response = JavaJsonUtils.serialize(result)
+ Future(Ok(response).as("application/json"))
+ }
+
+ def upload(identifier: String, fileFormat: Option[String], validation: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = requestFormData(identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "uploadContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.putAll(Map("identifier" -> identifier, "params" -> UploadParams(fileFormat, validation.map(_.toBoolean))).asJava)
+ getResult(ApiId.UPLOAD_CONTENT, contentActor, contentRequest)
+ }
+
+
+ def copy(identifier: String, mode: Option[String], copyType: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse(""), "copyType" -> copyType).asJava)
+ val contentRequest = getRequest(content, headers, "copy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.COPY_CONTENT, contentActor, contentRequest)
+ }
+
+ def uploadPreSigned(identifier: String, `type`: Option[String])= Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "type" -> `type`.getOrElse("assets")).asJava)
+ val contentRequest = getRequest(content, headers, "uploadPreSignedUrl")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.UPLOAD_PRE_SIGNED_CONTENT, contentActor, contentRequest)
+ }
+
+ def importContent() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "importContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.IMPORT_CONTENT, contentActor, contentRequest)
+ }
+
+}
diff --git a/learning-api/content-service/app/controllers/v3/LicenseController.scala b/content-api/content-service/app/controllers/v3/LicenseController.scala
similarity index 77%
rename from learning-api/content-service/app/controllers/v3/LicenseController.scala
rename to content-api/content-service/app/controllers/v3/LicenseController.scala
index 0cbfc6487..9543f2759 100644
--- a/learning-api/content-service/app/controllers/v3/LicenseController.scala
+++ b/content-api/content-service/app/controllers/v3/LicenseController.scala
@@ -4,11 +4,11 @@ import akka.actor.{ActorRef, ActorSystem}
import com.google.inject.Singleton
import controllers.BaseController
import javax.inject.{Inject, Named}
-import org.sunbird.utils.LicenseOperations
+import org.sunbird.content.util.LicenseConstants
import play.api.mvc.ControllerComponents
import utils.{ActorNames, ApiId}
-import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
import scala.concurrent.ExecutionContext
@Singleton
@@ -21,9 +21,9 @@ class LicenseController @Inject()(@Named(ActorNames.LICENSE_ACTOR) licenseActor:
def create() = Action.async { implicit request =>
val headers = commonHeaders()
val body = requestBody()
- val license = body.getOrElse("license", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ val license = body.getOrDefault("license", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
license.putAll(headers)
- val licenseRequest = getRequest(license, headers, LicenseOperations.createLicense.name())
+ val licenseRequest = getRequest(license, headers, LicenseConstants.CREATE_LICENSE)
setRequestContext(licenseRequest, version, objectType, schemaName)
getResult(ApiId.CREATE_LICENSE, licenseActor, licenseRequest)
}
@@ -32,8 +32,8 @@ class LicenseController @Inject()(@Named(ActorNames.LICENSE_ACTOR) licenseActor:
val headers = commonHeaders()
val license = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
license.putAll(headers)
- license.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse("")))
- val licenseRequest = getRequest(license, headers, LicenseOperations.readLicense.name())
+ license.putAll(Map("identifier" -> identifier, "fields" -> fields.getOrElse("")).asJava)
+ val licenseRequest = getRequest(license, headers, LicenseConstants.READ_LICENSE)
setRequestContext(licenseRequest, version, objectType, schemaName)
getResult(ApiId.READ_LICENSE, licenseActor, licenseRequest)
}
@@ -41,9 +41,9 @@ class LicenseController @Inject()(@Named(ActorNames.LICENSE_ACTOR) licenseActor:
def update(identifier: String) = Action.async { implicit request =>
val headers = commonHeaders()
val body = requestBody()
- val license = body.getOrElse("license", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ val license = body.getOrDefault("license", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
license.putAll(headers)
- val licenseRequest = getRequest(license, headers, LicenseOperations.updateLicense.name())
+ val licenseRequest = getRequest(license, headers, LicenseConstants.UPDATE_LICENSE)
setRequestContext(licenseRequest, version, objectType, schemaName)
licenseRequest.getContext.put("identifier", identifier)
getResult(ApiId.UPDATE_LICENSE, licenseActor, licenseRequest)
@@ -53,7 +53,7 @@ class LicenseController @Inject()(@Named(ActorNames.LICENSE_ACTOR) licenseActor:
val headers = commonHeaders()
val license = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
license.putAll(headers)
- val licenseRequest = getRequest(license, headers, LicenseOperations.retireLicense.name())
+ val licenseRequest = getRequest(license, headers, LicenseConstants.RETIRE_LICENSE)
setRequestContext(licenseRequest, version, objectType, schemaName)
licenseRequest.getContext.put("identifier", identifier)
getResult(ApiId.RETIRE_LICENSE, licenseActor, licenseRequest)
diff --git a/content-api/content-service/app/controllers/v4/AppController.scala b/content-api/content-service/app/controllers/v4/AppController.scala
new file mode 100644
index 000000000..54e9fc8cb
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/AppController.scala
@@ -0,0 +1,56 @@
+package controllers.v4
+
+import akka.actor.ActorRef
+import com.google.inject.Singleton
+import controllers.BaseController
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import javax.inject.{Inject, Named}
+import scala.concurrent.ExecutionContext
+
+import scala.collection.JavaConverters._
+
+/***
+ * TODO: Re-write this controller after merging the Event and EventSet Controller.
+ */
+
+@Singleton
+class AppController @Inject()(@Named(ActorNames.APP_ACTOR) appActor: ActorRef, cc: ControllerComponents)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "App"
+ val schemaName: String = "app"
+ val version = "1.0"
+ val apiVersion = "4.0"
+
+ def register() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val metadata = body.getOrDefault("app", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ metadata.putAll(headers)
+ val appRequest = getRequest(metadata, headers, "create")
+ setRequestContext(appRequest, version, objectType, schemaName)
+ getResult(ApiId.REGISTER_APP, appActor, appRequest, version = apiVersion)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val metadata = body.getOrDefault("app", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ metadata.putAll(headers)
+ val appRequest = getRequest(metadata, headers, "update")
+ setRequestContext(appRequest, version, objectType, schemaName)
+ appRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_APP, appActor, appRequest, version = apiVersion)
+ }
+
+ def read(identifier: String, fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val app = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ app.putAll(headers)
+ app.putAll(Map("identifier" -> identifier, "mode" -> "read", "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(app, headers, "read")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_APP, appActor, readRequest, version = apiVersion)
+ }
+}
diff --git a/content-api/content-service/app/controllers/v4/AssetController.scala b/content-api/content-service/app/controllers/v4/AssetController.scala
new file mode 100644
index 000000000..feaf9a6b7
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/AssetController.scala
@@ -0,0 +1,106 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import org.sunbird.models.UploadParams
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext}
+@Singleton
+class AssetController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor: ActorRef, @Named(ActorNames.ASSET_ACTOR) assetActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+ val objectType = "Asset"
+ val schemaName: String = "asset"
+ val version = "1.0"
+ val apiVersion = "4.0"
+
+ /**
+ * This Api end point takes a body
+ * Content Identifier the unique identifier of a content, can either be provided or will be generated
+ * primaryCategory, mimeType, name and code are mandatory
+ *
+ * @returns identifier and versionKey
+ */
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ if(!validatePrimaryCategory(content))
+ getErrorResponse(ApiId.CREATE_ASSET, apiVersion, "VALIDATION_ERROR", "primaryCategory is a mandatory parameter.")
+ else if(validateContentType(content))
+ getErrorResponse(ApiId.CREATE_ASSET, apiVersion, "VALIDATION_ERROR", "contentType cannot be set from request.")
+ else {
+ val contentRequest = getRequest(content, headers, "createContent", true)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_ASSET, contentActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ /**
+ * This Api end point takes 3 parameters
+ * Content Identifier the unique identifier of a content
+ * Mode in which the content can be viewed (default read or edit)
+ * Fields are metadata that should be returned to visualize
+ *
+ * @param identifier
+ * @param mode
+ * @param fields
+ * @return
+ */
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_ASSET, contentActor, readRequest, version = apiVersion)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_ASSET, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def upload(identifier: String, fileFormat: Option[String], validation: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = requestFormData(identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "uploadContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.putAll(Map("identifier" -> identifier, "params" -> UploadParams(fileFormat, validation.map(_.toBoolean))).asJava)
+ getResult(ApiId.UPLOAD_ASSET, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def uploadPreSigned(identifier: String, `type`: Option[String])= Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "type" -> `type`.getOrElse("assets")).asJava)
+ val contentRequest = getRequest(content, headers, "uploadPreSignedUrl")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.UPLOAD_PRE_SIGNED_ASSET, contentActor, contentRequest)
+ }
+
+ def copy(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val contentRequest = getRequest(content, headers, "copy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.COPY_ASSET, assetActor, contentRequest, version = apiVersion)
+ }
+}
diff --git a/content-api/content-service/app/controllers/v4/CollectionController.scala b/content-api/content-service/app/controllers/v4/CollectionController.scala
new file mode 100644
index 000000000..1362bcba8
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/CollectionController.scala
@@ -0,0 +1,187 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{ExecutionContext}
+@Singleton
+class CollectionController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor: ActorRef, @Named(ActorNames.COLLECTION_ACTOR) collectionActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+ val objectType = "Collection"
+ val schemaName: String = "collection"
+ val version = "1.0"
+ val apiVersion = "4.0"
+
+ /**
+ * This Api end point takes a body
+ * Content Identifier the unique identifier of a content, can either be provided or will be generated
+ * primaryCategory, mimeType, name and code are mandatory
+ *
+ * @returns identifier and versionKey
+ */
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ if(!validatePrimaryCategory(content))
+ getErrorResponse(ApiId.CREATE_COLLECTION, apiVersion, "VALIDATION_ERROR", "primaryCategory is a mandatory parameter")
+ else if(validateContentType(content))
+ getErrorResponse(ApiId.CREATE_COLLECTION, apiVersion, "VALIDATION_ERROR", "contentType cannot be set from request.")
+ else {
+ val contentRequest = getRequest(content, headers, "createContent", true)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_COLLECTION, contentActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ /**
+ * This Api end point takes 3 parameters
+ * Content Identifier the unique identifier of a content
+ * Mode in which the content can be viewed (default read or edit)
+ * Fields are metadata that should be returned to visualize
+ *
+ * @param identifier
+ * @param mode
+ * @param fields
+ * @return
+ */
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_COLLECTION, contentActor, readRequest, version = apiVersion)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_COLLECTION, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def addHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "addHierarchy")
+ contentRequest.put("mode", "edit")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.ADD_HIERARCHY_V4, collectionActor, contentRequest)
+ }
+
+ def removeHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "removeHierarchy")
+ contentRequest.put("mode", "edit")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.REMOVE_HIERARCHY_V4, collectionActor, contentRequest, version = apiVersion)
+ }
+
+ def updateHierarchy() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val data = body.getOrDefault("data", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ data.putAll(headers)
+ val contentRequest = getRequest(data, headers, "updateHierarchy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.UPDATE_HIERARCHY_V4, collectionActor, contentRequest, version = apiVersion)
+ }
+
+ def getHierarchy(identifier: String, mode: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("rootId" -> identifier, "mode" -> mode.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, null)
+ getResult(ApiId.GET_HIERARCHY_V4, collectionActor, readRequest, version = apiVersion)
+ }
+
+ def getBookmarkHierarchy(identifier: String, bookmarkId: String, mode: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("rootId" -> identifier, "bookmarkId" -> bookmarkId, "mode" -> mode.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, null)
+ getResult(ApiId.GET_HIERARCHY_V4, collectionActor, readRequest, version = apiVersion)
+ }
+
+ def flag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val contentRequest = getRequest(content, headers, "flagContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.FlAG_COLLECTION, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def acceptFlag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val acceptRequest = getRequest(content, headers, "acceptFlag")
+ setRequestContext(acceptRequest, version, objectType, schemaName)
+ getResult(ApiId.ACCEPT_FLAG_COLLECTION, contentActor, acceptRequest, version = apiVersion)
+ }
+
+ def discard(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val discardRequest = getRequest(content, headers, "discardContent")
+ setRequestContext(discardRequest, version, objectType, schemaName)
+ getResult(ApiId.DISCARD_COLLECTION, contentActor, discardRequest, version = apiVersion)
+ }
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.put("identifier", identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "retireContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.RETIRE_COLLECTION, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def collectionLinkDialCode(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "linkDIALCode")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("linkType", "collection")
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.LINK_DIAL_COLLECTION, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def copy(identifier: String, mode: Option[String], copyType: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse(""), "copyType" -> copyType).asJava)
+ val contentRequest = getRequest(content, headers, "copy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.COPY_COLLECTION, contentActor, contentRequest)
+ }
+}
diff --git a/content-api/content-service/app/controllers/v4/ContentController.scala b/content-api/content-service/app/controllers/v4/ContentController.scala
new file mode 100644
index 000000000..3cf2d92a6
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/ContentController.scala
@@ -0,0 +1,166 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import controllers.BaseController
+import javax.inject.{Inject, Named}
+import org.sunbird.models.UploadParams
+import play.api.mvc.ControllerComponents
+import utils.{ActorNames, ApiId}
+
+import scala.collection.JavaConverters._
+
+import scala.concurrent.{ExecutionContext}
+
+@Singleton
+class ContentController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
+
+ val objectType = "Content"
+ val schemaName: String = "content"
+ val version = "1.0"
+ val apiVersion = "4.0"
+
+
+ def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ if(!validatePrimaryCategory(content))
+ getErrorResponse(ApiId.CREATE_CONTENT, apiVersion, "VALIDATION_ERROR", "primaryCategory is a mandatory parameter")
+ else if(validateContentType(content))
+ getErrorResponse(ApiId.CREATE_CONTENT, apiVersion, "VALIDATION_ERROR", "contentType cannot be set from request.")
+ else {
+ val contentRequest = getRequest(content, headers, "createContent", true)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_ASSET, contentActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ /**
+ * This Api end point takes 3 parameters
+ * Content Identifier the unique identifier of a content
+ * Mode in which the content can be viewed (default read or edit)
+ * Fields are metadata that should be returned to visualize
+ *
+ * @param identifier Identifier of the content
+ * @param mode Mode to read the data edit or published
+ * @param fields List of fields to return in the response
+ */
+ def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_CONTENT, contentActor, readRequest, version = apiVersion)
+ }
+
+ def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.UPDATE_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def flag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val contentRequest = getRequest(content, headers, "flagContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.FlAG_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def acceptFlag(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val acceptRequest = getRequest(content, headers, "acceptFlag")
+ setRequestContext(acceptRequest, version, objectType, schemaName)
+ getResult(ApiId.ACCEPT_FLAG, contentActor, acceptRequest)
+ }
+
+
+ def discard(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier).asJava)
+ val discardRequest = getRequest(content, headers, "discardContent")
+ setRequestContext(discardRequest, version, objectType, schemaName)
+ getResult(ApiId.DISCARD_CONTENT, contentActor, discardRequest)
+ }
+ def retire(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.put("identifier", identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "retireContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.RETIRE_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def linkDialCode() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "linkDIALCode")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("linkType", "content")
+ getResult(ApiId.LINK_DIAL_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def upload(identifier: String, fileFormat: Option[String], validation: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = requestFormData(identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "uploadContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.putAll(Map("identifier" -> identifier, "params" -> UploadParams(fileFormat, validation.map(_.toBoolean))).asJava)
+ getResult(ApiId.UPLOAD_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+
+ def copy(identifier: String, mode: Option[String], copyType: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse(""), "copyType" -> copyType).asJava)
+ val contentRequest = getRequest(content, headers, "copy")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.COPY_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def uploadPreSigned(identifier: String, `type`: Option[String])= Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "type" -> `type`.getOrElse("assets")).asJava)
+ val contentRequest = getRequest(content, headers, "uploadPreSignedUrl")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.UPLOAD_PRE_SIGNED_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+ def importContent() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ body.putAll(headers)
+ val contentRequest = getRequest(body, headers, "importContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.IMPORT_CONTENT, contentActor, contentRequest, version = apiVersion)
+ }
+
+}
diff --git a/content-api/content-service/app/controllers/v4/EventController.scala b/content-api/content-service/app/controllers/v4/EventController.scala
new file mode 100644
index 000000000..3b609d32b
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/EventController.scala
@@ -0,0 +1,70 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import play.api.mvc.{Action, AnyContent, ControllerComponents}
+import utils.{ActorNames, ApiId, Constants}
+
+import javax.inject.{Inject, Named}
+import scala.collection.JavaConverters.mapAsJavaMapConverter
+import scala.concurrent.ExecutionContext
+
+@Singleton
+class EventController @Inject()(@Named(ActorNames.EVENT_ACTOR) eventActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends ContentController(eventActor, cc, actorSystem) {
+
+ override val objectType = "Event"
+ override val schemaName: String = "event"
+
+ override def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ content.putAll(headers)
+ if(validateContentType(content))
+ getErrorResponse(ApiId.CREATE_EVENT, apiVersion, "VALIDATION_ERROR", "contentType cannot be set from request.")
+ else {
+ val contentRequest = getRequest(content, headers, "createContent", false)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_EVENT, eventActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ override def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap[String, Object]()
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ readRequest.getContext.put(Constants.RESPONSE_SCHEMA_NAME, schemaName);
+ getResult(ApiId.READ_CONTENT, eventActor, readRequest, version = apiVersion)
+ }
+
+ override def update(identifier: String) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
+ if (content.containsKey("status")) {
+ getErrorResponse(ApiId.UPDATE_EVENT, apiVersion, "VALIDATION_ERROR", "status update is restricted, use status APIs.")
+ } else {
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.UPDATE_EVENT, eventActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ def publish(identifier: String): Action[AnyContent] = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap[String, Object]()
+ content.put("status", "Live")
+ content.put("identifier", identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "publishContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier);
+ getResult(ApiId.PUBLISH_EVENT, eventActor, contentRequest, version = apiVersion)
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-service/app/controllers/v4/EventSetController.scala b/content-api/content-service/app/controllers/v4/EventSetController.scala
new file mode 100644
index 000000000..76f014098
--- /dev/null
+++ b/content-api/content-service/app/controllers/v4/EventSetController.scala
@@ -0,0 +1,78 @@
+package controllers.v4
+
+import akka.actor.{ActorRef, ActorSystem}
+import com.google.inject.Singleton
+import play.api.mvc.{Action, AnyContent, ControllerComponents}
+import utils.{ActorNames, ApiId, Constants}
+
+import javax.inject.{Inject, Named}
+import scala.collection.JavaConverters.mapAsJavaMapConverter
+import scala.concurrent.ExecutionContext
+
+@Singleton
+class EventSetController @Inject()(@Named(ActorNames.EVENT_SET_ACTOR) eventSetActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends CollectionController(eventSetActor, eventSetActor, cc, actorSystem) {
+ override val objectType = "EventSet"
+ override val schemaName: String = "eventset"
+
+ override def create() = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ if(validateContentType(content))
+ getErrorResponse(ApiId.CREATE_EVENT_SET, apiVersion, "VALIDATION_ERROR", "contentType cannot be set from request.")
+ else {
+ val contentRequest = getRequest(content, headers, "createContent", false)
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.CREATE_EVENT_SET, eventSetActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ override def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "readContent")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ readRequest.getContext.put(Constants.RESPONSE_SCHEMA_NAME, schemaName);
+ getResult(ApiId.READ_COLLECTION, eventSetActor, readRequest, version = apiVersion)
+ }
+
+ def getHierarchy(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
+ content.putAll(headers)
+ content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
+ val readRequest = getRequest(content, headers, "getHierarchy")
+ setRequestContext(readRequest, version, objectType, schemaName)
+ getResult(ApiId.READ_COLLECTION, eventSetActor, readRequest, version = apiVersion)
+ }
+
+ override def update(identifier: String): Action[AnyContent] = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val body = requestBody()
+ val content = body.getOrDefault(schemaName, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]
+ if (content.containsKey("status")) {
+ getErrorResponse(ApiId.UPDATE_EVENT_SET, apiVersion, "VALIDATION_ERROR", "status update is restricted, use status APIs.")
+ } else {
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "updateContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ contentRequest.getContext.put("identifier", identifier)
+ getResult(ApiId.UPDATE_EVENT_SET, eventSetActor, contentRequest, version = apiVersion)
+ }
+ }
+
+ def publish(identifier: String): Action[AnyContent] = Action.async { implicit request =>
+ val headers = commonHeaders()
+ val content = new java.util.HashMap[String, Object]()
+ content.put("identifier", identifier)
+ content.putAll(headers)
+ val contentRequest = getRequest(content, headers, "publishContent")
+ setRequestContext(contentRequest, version, objectType, schemaName)
+ getResult(ApiId.PUBLISH_EVENT_SET, eventSetActor, contentRequest, version = apiVersion)
+ }
+
+
+}
\ No newline at end of file
diff --git a/content-api/content-service/app/filters/AccessLogFilter.scala b/content-api/content-service/app/filters/AccessLogFilter.scala
new file mode 100644
index 000000000..5e30b5579
--- /dev/null
+++ b/content-api/content-service/app/filters/AccessLogFilter.scala
@@ -0,0 +1,45 @@
+package filters
+
+import akka.util.ByteString
+import javax.inject.Inject
+import org.sunbird.telemetry.util.TelemetryAccessEventUtil
+import play.api.Logging
+import play.api.libs.streams.Accumulator
+import play.api.mvc._
+
+import scala.concurrent.ExecutionContext
+import scala.collection.JavaConverters._
+
+class AccessLogFilter @Inject() (implicit ec: ExecutionContext) extends EssentialFilter with Logging {
+
+ val xHeaderNames = Map("x-session-id" -> "X-Session-ID", "X-Consumer-ID" -> "x-consumer-id", "x-device-id" -> "X-Device-ID", "x-app-id" -> "APP_ID", "x-authenticated-userid" -> "X-Authenticated-Userid", "x-channel-id" -> "X-Channel-Id")
+
+ def apply(nextFilter: EssentialAction) = new EssentialAction {
+ def apply(requestHeader: RequestHeader) = {
+
+ val startTime = System.currentTimeMillis
+
+ val accumulator: Accumulator[ByteString, Result] = nextFilter(requestHeader)
+
+ accumulator.map { result =>
+ val endTime = System.currentTimeMillis
+ val requestTime = endTime - startTime
+
+ val path = requestHeader.uri
+ if(!path.contains("/health")){
+ val headers = requestHeader.headers.headers.groupBy(_._1).mapValues(_.map(_._2))
+ val appHeaders = headers.filter(header => xHeaderNames.keySet.contains(header._1.toLowerCase))
+ .map(entry => (xHeaderNames.get(entry._1.toLowerCase()).get, entry._2.head))
+ val otherDetails = Map[String, Any]("StartTime" -> startTime, "env" -> "content",
+ "RemoteAddress" -> requestHeader.remoteAddress,
+ "ContentLength" -> result.body.contentLength.getOrElse(0),
+ "Status" -> result.header.status, "Protocol" -> "http",
+ "path" -> path,
+ "Method" -> requestHeader.method.toString)
+ TelemetryAccessEventUtil.writeTelemetryEventLog((otherDetails ++ appHeaders).asInstanceOf[Map[String, AnyRef]].asJava)
+ }
+ result.withHeaders("Request-Time" -> requestTime.toString)
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/content-api/content-service/app/handlers/SignalHandler.scala b/content-api/content-service/app/handlers/SignalHandler.scala
new file mode 100644
index 000000000..4cad301c1
--- /dev/null
+++ b/content-api/content-service/app/handlers/SignalHandler.scala
@@ -0,0 +1,33 @@
+package handlers
+
+import java.util.concurrent.TimeUnit
+
+import akka.actor.ActorSystem
+import javax.inject.{Inject, Singleton}
+import org.slf4j.LoggerFactory
+import play.api.inject.DefaultApplicationLifecycle
+import sun.misc.Signal
+
+import scala.concurrent.duration.Duration
+
+@Singleton
+class SignalHandler @Inject()(implicit actorSystem: ActorSystem, lifecycle: DefaultApplicationLifecycle) {
+ val LOG = LoggerFactory.getLogger(classOf[SignalHandler])
+ val STOP_DELAY = Duration.create(30, TimeUnit.SECONDS)
+ var isShuttingDown = false
+
+ println("Initializing SignalHandler...")
+ Signal.handle(new Signal("TERM"), new sun.misc.SignalHandler() {
+ override def handle(signal: Signal): Unit = {
+ // $COVERAGE-OFF$ Disabling scoverage as this code is impossible to test
+ isShuttingDown = true
+ println("Termination required, swallowing SIGTERM to allow current requests to finish. : " + System.currentTimeMillis())
+ actorSystem.scheduler.scheduleOnce(STOP_DELAY)(() => {
+ println("ApplicationLifecycle stop triggered... : " + System.currentTimeMillis())
+ lifecycle.stop()
+ })(actorSystem.dispatcher)
+ // $COVERAGE-ON
+ }
+ })
+}
+
diff --git a/content-api/content-service/app/modules/ContentModule.scala b/content-api/content-service/app/modules/ContentModule.scala
new file mode 100644
index 000000000..a701a048e
--- /dev/null
+++ b/content-api/content-service/app/modules/ContentModule.scala
@@ -0,0 +1,27 @@
+package modules
+
+import com.google.inject.AbstractModule
+import org.sunbird.channel.actors.ChannelActor
+import org.sunbird.content.actors.{AppActor, AssetActor, CategoryActor, CollectionActor, ContentActor, EventActor, EventSetActor, HealthActor, LicenseActor}
+import play.libs.akka.AkkaGuiceSupport
+import utils.ActorNames
+
+class ContentModule extends AbstractModule with AkkaGuiceSupport {
+
+ override def configure() = {
+ // $COVERAGE-OFF$ Disabling scoverage as this code is impossible to test
+ //super.configure()
+ bindActor(classOf[HealthActor], ActorNames.HEALTH_ACTOR)
+ bindActor(classOf[ContentActor], ActorNames.CONTENT_ACTOR)
+ bindActor(classOf[LicenseActor], ActorNames.LICENSE_ACTOR)
+ bindActor(classOf[CollectionActor], ActorNames.COLLECTION_ACTOR)
+ bindActor(classOf[EventActor], ActorNames.EVENT_ACTOR)
+ bindActor(classOf[EventSetActor], ActorNames.EVENT_SET_ACTOR)
+ bindActor(classOf[ChannelActor], ActorNames.CHANNEL_ACTOR)
+ bindActor(classOf[CategoryActor], ActorNames.CATEGORY_ACTOR)
+ bindActor(classOf[AssetActor], ActorNames.ASSET_ACTOR)
+ bindActor(classOf[AppActor], ActorNames.APP_ACTOR)
+ println("Initialized application actors...")
+ // $COVERAGE-ON
+ }
+}
diff --git a/content-api/content-service/app/utils/ActorNames.scala b/content-api/content-service/app/utils/ActorNames.scala
new file mode 100644
index 000000000..a98a6ddf8
--- /dev/null
+++ b/content-api/content-service/app/utils/ActorNames.scala
@@ -0,0 +1,16 @@
+package utils
+
+object ActorNames {
+
+ final val HEALTH_ACTOR = "healthActor"
+ final val CONTENT_ACTOR = "contentActor"
+ final val LICENSE_ACTOR = "licenseActor"
+ final val COLLECTION_ACTOR = "collectionActor"
+ final val EVENT_ACTOR = "eventActor"
+ final val EVENT_SET_ACTOR = "eventSetActor"
+ final val CHANNEL_ACTOR = "channelActor"
+ final val CATEGORY_ACTOR = "categoryActor"
+ final val ASSET_ACTOR = "assetActor"
+ final val APP_ACTOR = "appActor"
+
+}
diff --git a/content-api/content-service/app/utils/ApiId.scala b/content-api/content-service/app/utils/ApiId.scala
new file mode 100644
index 000000000..a4231b43f
--- /dev/null
+++ b/content-api/content-service/app/utils/ApiId.scala
@@ -0,0 +1,88 @@
+package utils
+
+object ApiId {
+
+ final val APPLICATION_HEALTH = "api.content.health"
+ final val APPLICATION_SERVICE_HEALTH = "api.content.service.health"
+
+ //Content APIs
+ val CREATE_CONTENT = "api.content.create"
+ val READ_CONTENT = "api.content.read"
+ val UPDATE_CONTENT = "api.content.update"
+ val UPLOAD_CONTENT = "api.content.upload"
+ val RETIRE_CONTENT = "api.content.retire"
+ val COPY_CONTENT = "api.content.copy"
+ val UPLOAD_PRE_SIGNED_CONTENT = "api.content.upload.url"
+ val DISCARD_CONTENT = "api.content.discard"
+ val FlAG_CONTENT = "api.content.flag"
+ val ACCEPT_FLAG = "api.content.flag.accept"
+ val LINK_DIAL_CONTENT = "api.content.dialcode.link"
+ val IMPORT_CONTENT = "api.content.import"
+
+ // Collection APIs
+ val ADD_HIERARCHY = "api.content.hierarchy.add"
+ val REMOVE_HIERARCHY = "api.content.hierarchy.remove"
+ val UPDATE_HIERARCHY = "api.content.hierarchy.update"
+ val GET_HIERARCHY = "api.content.hierarchy.get"
+ val LINK_DIAL_COLLECTION = "api.collection.dialcode.link"
+
+ //License APIs
+ val CREATE_LICENSE = "api.license.create"
+ val READ_LICENSE = "api.license.read"
+ val UPDATE_LICENSE = "api.license.update"
+ val RETIRE_LICENSE = "api.license.retire"
+
+ //Channel APIs
+ val CREATE_CHANNEL = "api.channel.create"
+ val READ_CHANNEL = "api.channel.read"
+ val UPDATE_CHANNEL = "api.channel.update"
+ val LIST_CHANNEL = "api.channel.list"
+ val RETIRE_CHANNEL = "api.channel.retire"
+
+ //Category APIs
+ val CREATE_CATEGORY = "api.category.create"
+ val READ_CATEGORY = "api.category.read"
+ val UPDATE_CATEGORY = "api.category.update"
+ val RETIRE_CATEGORY = "api.category.retire"
+
+ //Asset V4 apis
+ val CREATE_ASSET = "api.asset.create"
+ val READ_ASSET = "api.asset.read"
+ val UPDATE_ASSET = "api.asset.update"
+ val UPLOAD_ASSET = "api.asset.upload"
+ val UPLOAD_PRE_SIGNED_ASSET= "api.asset.upload.url"
+ val COPY_ASSET = "api.asset.copy"
+
+
+
+ //Collection V4 apis
+ val CREATE_COLLECTION = "api.collection.create"
+ val READ_COLLECTION = "api.collection.read"
+ val UPDATE_COLLECTION = "api.collection.update"
+ val RETIRE_COLLECTION = "api.collection.retire"
+ val COPY_COLLECTION = "api.collection.copy"
+ val DISCARD_COLLECTION = "api.collection.discard"
+ val FlAG_COLLECTION = "api.collection.flag"
+ val ACCEPT_FLAG_COLLECTION = "api.collection.flag.accept"
+ val ADD_HIERARCHY_V4 = "api.collection.hierarchy.add"
+ val REMOVE_HIERARCHY_V4 = "api.collection.hierarchy.remove"
+ val UPDATE_HIERARCHY_V4 = "api.collection.hierarchy.update"
+ val GET_HIERARCHY_V4 = "api.collection.hierarchy.get"
+
+ //App APIs
+ val REGISTER_APP = "api.app.register"
+ val READ_APP = "api.app.read"
+ val UPDATE_APP = "api.app.update"
+ val APPROVE_APP = "api.app.approve"
+ val REJECT_APP = "api.app.reject"
+ val RETIRE_APP = "api.app.retire"
+
+ val CREATE_EVENT = "api.event.create"
+ val UPDATE_EVENT = "api.event.update"
+
+ val CREATE_EVENT_SET = "api.eventset.create"
+ val UPDATE_EVENT_SET = "api.eventset.update"
+ val PUBLISH_EVENT_SET = "api.eventset.publish"
+ val PUBLISH_EVENT = "api.event.publish"
+
+}
diff --git a/content-api/content-service/app/utils/Constants.scala b/content-api/content-service/app/utils/Constants.scala
new file mode 100644
index 000000000..b10c214e5
--- /dev/null
+++ b/content-api/content-service/app/utils/Constants.scala
@@ -0,0 +1,18 @@
+package utils
+
+object Constants {
+ val SCHEMA_NAME: String = "schemaName"
+ val RESPONSE_SCHEMA_NAME: String = "responseSchemaName"
+ val VERSION: String = "version"
+ val CONTENT_SCHEMA_NAME: String = "content"
+ val COLLECTION_SCHEMA_NAME: String = "collection"
+ val ASSET_SCHEMA_NAME: String = "asset"
+ val CONTENT_VERSION: String = "1.0"
+ val COLLECTION_VERSION: String = "1.0"
+ val ASSET_VERSION: String = "1.0"
+ val COLLECTION_MIME_TYPE: String = "application/vnd.ekstep.content-collection"
+ val ASSET_CONTENT_TYPE: String = "Asset"
+ val CONTENT_OBJECT_TYPE: String = "Content"
+ val COLLECTION_OBJECT_TYPE: String = "Collection"
+ val ASSET_OBJECT_TYPE: String = "Asset"
+}
\ No newline at end of file
diff --git a/content-api/content-service/app/utils/JavaJsonUtils.scala b/content-api/content-service/app/utils/JavaJsonUtils.scala
new file mode 100644
index 000000000..2093c2e33
--- /dev/null
+++ b/content-api/content-service/app/utils/JavaJsonUtils.scala
@@ -0,0 +1,38 @@
+package utils
+
+import java.lang.reflect.{ParameterizedType, Type}
+
+import com.fasterxml.jackson.core.`type`.TypeReference
+import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
+
+object JavaJsonUtils {
+
+ @transient val mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+// mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
+// mapper.setSerializationInclusion(Include.NON_NULL);
+
+ @throws(classOf[Exception])
+ def serialize(obj: AnyRef): String = {
+ mapper.writeValueAsString(obj);
+ }
+
+ @throws(classOf[Exception])
+ def deserialize[T: Manifest](value: String): T = mapper.readValue(value, typeReference[T]);
+
+ private[this] def typeReference[T: Manifest] = new TypeReference[T] {
+ override def getType = typeFromManifest(manifest[T])
+ }
+
+
+ private[this] def typeFromManifest(m: Manifest[_]): Type = {
+ if (m.typeArguments.isEmpty) { m.runtimeClass }
+ // $COVERAGE-OFF$Disabling scoverage as this code is impossible to test
+ else new ParameterizedType {
+ def getRawType = m.runtimeClass
+ def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
+ def getOwnerType = null
+ }
+ // $COVERAGE-ON$
+ }
+}
diff --git a/content-api/content-service/conf/application.conf b/content-api/content-service/conf/application.conf
new file mode 100644
index 000000000..770162730
--- /dev/null
+++ b/content-api/content-service/conf/application.conf
@@ -0,0 +1,676 @@
+# This is the main configuration file for the application.
+# https://www.playframework.com/documentation/latest/ConfigFile
+# ~~~~~
+# Play uses HOCON as its configuration file format. HOCON has a number
+# of advantages over other config formats, but there are two things that
+# can be used when modifying settings.
+#
+# You can include other configuration files in this main application.conf file:
+#include "extra-config.conf"
+#
+# You can declare variables and substitute for them:
+#mykey = ${some.value}
+#
+# And if an environment variable exists when there is no other substitution, then
+# HOCON will fall back to substituting environment variable:
+#mykey = ${JAVA_HOME}
+
+## Akka
+# https://www.playframework.com/documentation/latest/ScalaAkka#Configuration
+# https://www.playframework.com/documentation/latest/JavaAkka#Configuration
+# ~~~~~
+# Play uses Akka internally and exposes Akka Streams and actors in Websockets and
+# other streaming HTTP responses.
+akka {
+ # "akka.log-config-on-start" is extraordinarly useful because it log the complete
+ # configuration at INFO level, including defaults and overrides, so it s worth
+ # putting at the very top.
+ #
+ # Put the following in your conf/logback.xml file:
+ #
+ #
+ #
+ # And then uncomment this line to debug the configuration.
+ #
+ #log-config-on-start = true
+ default-dispatcher {
+ # This will be used if you have set "executor = "fork-join-executor""
+ fork-join-executor {
+ # Min number of threads to cap factor-based parallelism number to
+ parallelism-min = 8
+
+ # The parallelism factor is used to determine thread pool size using the
+ # following formula: ceil(available processors * factor). Resulting size
+ # is then bounded by the parallelism-min and parallelism-max values.
+ parallelism-factor = 32.0
+
+ # Max number of threads to cap factor-based parallelism number to
+ parallelism-max = 64
+
+ # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack
+ # like peeking mode which "pop".
+ task-peeking-mode = "FIFO"
+ }
+ }
+ actors-dispatcher {
+ type = "Dispatcher"
+ executor = "fork-join-executor"
+ fork-join-executor {
+ parallelism-min = 8
+ parallelism-factor = 32.0
+ parallelism-max = 64
+ }
+ # Throughput for default Dispatcher, set to 1 for as fair as possible
+ throughput = 1
+ }
+ actor {
+ deployment {
+ /contentActor
+ {
+ router = smallest-mailbox-pool
+ nr-of-instances = 10
+ dispatcher = actors-dispatcher
+ }
+ /healthActor
+ {
+ nr-of-instances = 10
+ dispatcher = actors-dispatcher
+ }
+ /assetActor
+ {
+ router = smallest-mailbox-pool
+ nr-of-instances = 10
+ dispatcher = actors-dispatcher
+ }
+ }
+ }
+}
+
+## Secret key
+# http://www.playframework.com/documentation/latest/ApplicationSecret
+# ~~~~~
+# The secret key is used to sign Play's session cookie.
+# This must be changed for production, but we don't recommend you change it in this file.
+play.http.secret.key = a-long-secret-to-calm-the-rage-of-the-entropy-gods
+
+## Modules
+# https://www.playframework.com/documentation/latest/Modules
+# ~~~~~
+# Control which modules are loaded when Play starts. Note that modules are
+# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
+# Please see https://www.playframework.com/documentation/latest/GlobalSettings
+# for more information.
+#
+# You can also extend Play functionality by using one of the publically available
+# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
+play.modules {
+ # By default, Play will load any class called Module that is defined
+ # in the root package (the "app" directory), or you can define them
+ # explicitly below.
+ # If there are any built-in modules that you want to enable, you can list them here.
+ enabled += modules.ContentModule
+
+ # If there are any built-in modules that you want to disable, you can list them here.
+ #disabled += ""
+}
+
+## IDE
+# https://www.playframework.com/documentation/latest/IDE
+# ~~~~~
+# Depending on your IDE, you can add a hyperlink for errors that will jump you
+# directly to the code location in the IDE in dev mode. The following line makes
+# use of the IntelliJ IDEA REST interface:
+#play.editor="http://localhost:63342/api/file/?file=%s&line=%s"
+
+## Internationalisation
+# https://www.playframework.com/documentation/latest/JavaI18N
+# https://www.playframework.com/documentation/latest/ScalaI18N
+# ~~~~~
+# Play comes with its own i18n settings, which allow the user's preferred language
+# to map through to internal messages, or allow the language to be stored in a cookie.
+play.i18n {
+ # The application languages
+ langs = [ "en" ]
+
+ # Whether the language cookie should be secure or not
+ #langCookieSecure = true
+
+ # Whether the HTTP only attribute of the cookie should be set to true
+ #langCookieHttpOnly = true
+}
+
+## Play HTTP settings
+# ~~~~~
+play.http {
+ ## Router
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # Define the Router object to use for this application.
+ # This router will be looked up first when the application is starting up,
+ # so make sure this is the entry point.
+ # Furthermore, it's assumed your route file is named properly.
+ # So for an application router like `my.application.Router`,
+ # you may need to define a router file `conf/my.application.routes`.
+ # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
+ #router = my.application.Router
+
+ ## Action Creator
+ # https://www.playframework.com/documentation/latest/JavaActionCreator
+ # ~~~~~
+ #actionCreator = null
+
+ ## ErrorHandler
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # If null, will attempt to load a class called ErrorHandler in the root package,
+ #errorHandler = null
+
+ ## Session & Flash
+ # https://www.playframework.com/documentation/latest/JavaSessionFlash
+ # https://www.playframework.com/documentation/latest/ScalaSessionFlash
+ # ~~~~~
+ session {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+
+ # Sets the max-age field of the cookie to 5 minutes.
+ # NOTE: this only sets when the browser will discard the cookie. Play will consider any
+ # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
+ # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
+ #maxAge = 300
+
+ # Sets the domain on the session cookie.
+ #domain = "example.com"
+ }
+
+ flash {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+ }
+}
+
+play.http.parser.maxDiskBuffer = 10MB
+parsers.anyContent.maxLength = 10MB
+
+play.server.provider = play.core.server.NettyServerProvider
+
+## Netty Provider
+# https://www.playframework.com/documentation/latest/SettingsNetty
+# ~~~~~
+play.server.netty {
+ # Whether the Netty wire should be logged
+ log.wire = true
+
+ # If you run Play on Linux, you can use Netty's native socket transport
+ # for higher performance with less garbage.
+ transport = "native"
+}
+
+## WS (HTTP Client)
+# https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS
+# ~~~~~
+# The HTTP client primarily used for REST APIs. The default client can be
+# configured directly, but you can also create different client instances
+# with customized settings. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += ws // or javaWs if using java
+#
+play.ws {
+ # Sets HTTP requests not to follow 302 requests
+ #followRedirects = false
+
+ # Sets the maximum number of open HTTP connections for the client.
+ #ahc.maxConnectionsTotal = 50
+
+ ## WS SSL
+ # https://www.playframework.com/documentation/latest/WsSSL
+ # ~~~~~
+ ssl {
+ # Configuring HTTPS with Play WS does not require programming. You can
+ # set up both trustManager and keyManager for mutual authentication, and
+ # turn on JSSE debugging in development with a reload.
+ #debug.handshake = true
+ #trustManager = {
+ # stores = [
+ # { type = "JKS", path = "exampletrust.jks" }
+ # ]
+ #}
+ }
+}
+
+## Cache
+# https://www.playframework.com/documentation/latest/JavaCache
+# https://www.playframework.com/documentation/latest/ScalaCache
+# ~~~~~
+# Play comes with an integrated cache API that can reduce the operational
+# overhead of repeated requests. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += cache
+#
+play.cache {
+ # If you want to bind several caches, you can bind the individually
+ #bindCaches = ["db-cache", "user-cache", "session-cache"]
+}
+
+## Filter Configuration
+# https://www.playframework.com/documentation/latest/Filters
+# ~~~~~
+# There are a number of built-in filters that can be enabled and configured
+# to give Play greater security.
+#
+play.filters {
+
+ # Enabled filters are run automatically against Play.
+ # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
+ enabled = [filters.AccessLogFilter]
+
+ # Disabled filters remove elements from the enabled list.
+ # disabled += filters.CSRFFilter
+
+
+ ## CORS filter configuration
+ # https://www.playframework.com/documentation/latest/CorsFilter
+ # ~~~~~
+ # CORS is a protocol that allows web applications to make requests from the browser
+ # across different domains.
+ # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
+ # dependencies on CORS settings.
+ cors {
+ # Filter paths by a whitelist of path prefixes
+ #pathPrefixes = ["/some/path", ...]
+
+ # The allowed origins. If null, all origins are allowed.
+ #allowedOrigins = ["http://www.example.com"]
+
+ # The allowed HTTP methods. If null, all methods are allowed
+ #allowedHttpMethods = ["GET", "POST"]
+ }
+
+ ## Security headers filter configuration
+ # https://www.playframework.com/documentation/latest/SecurityHeaders
+ # ~~~~~
+ # Defines security headers that prevent XSS attacks.
+ # If enabled, then all options are set to the below configuration by default:
+ headers {
+ # The X-Frame-Options header. If null, the header is not set.
+ #frameOptions = "DENY"
+
+ # The X-XSS-Protection header. If null, the header is not set.
+ #xssProtection = "1; mode=block"
+
+ # The X-Content-Type-Options header. If null, the header is not set.
+ #contentTypeOptions = "nosniff"
+
+ # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
+ #permittedCrossDomainPolicies = "master-only"
+
+ # The Content-Security-Policy header. If null, the header is not set.
+ #contentSecurityPolicy = "default-src 'self'"
+ }
+
+ ## Allowed hosts filter configuration
+ # https://www.playframework.com/documentation/latest/AllowedHostsFilter
+ # ~~~~~
+ # Play provides a filter that lets you configure which hosts can access your application.
+ # This is useful to prevent cache poisoning attacks.
+ hosts {
+ # Allow requests to example.com, its subdomains, and localhost:9000.
+ #allowed = [".example.com", "localhost:9000"]
+ }
+}
+
+play.http.parser.maxMemoryBuffer = 50MB
+akka.http.parsing.max-content-length = 50MB
+
+schema.base_path = "../../schemas/"
+content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"]
+
+languageCode {
+ assamese : "as"
+ bengali : "bn"
+ english : "en"
+ gujarati : "gu"
+ hindi : "hi"
+ kannada : "ka"
+ marathi : "mr"
+ odia : "or"
+ tamil : "ta"
+ telugu : "te"
+}
+
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd"]
+
+collection.keyspace = "hierarchy_store"
+content.keyspace = "content_store"
+
+# Learning-Service Configuration
+content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit", "event"]
+
+# Cassandra Configuration
+//content.keyspace.name=content_store
+//content.keyspace.table=content_data
+#TODO: Add Configuration for assessment. e.g: question_data
+orchestrator.keyspace.name=script_store
+orchestrator.keyspace.table=script_data
+cassandra.lp.connection="127.0.0.1:9042"
+cassandra.lpa.connection="127.0.0.1:9042"
+
+# Redis Configuration
+redis.host=localhost
+redis.port=6379
+redis.maxConnections=128
+
+#Condition to enable publish locally
+content.publish_task.enabled=true
+
+#directory location where store unzip file
+dist.directory=/data/tmp/dist/
+output.zipfile=/data/tmp/story.zip
+source.folder=/data/tmp/temp2/
+save.directory=/data/tmp/temp/
+
+# Content 2 vec analytics URL
+CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec"
+
+# FOR CONTENT WORKFLOW PIPELINE (CWP)
+
+#--Content Workflow Pipeline Mode
+OPERATION_MODE=TEST
+
+#--Maximum Content Package File Size Limit in Bytes (50 MB)
+MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800
+
+#--Maximum Asset File Size Limit in Bytes (20 MB)
+MAX_ASSET_FILE_SIZE_LIMIT=20971520
+
+#--No of Retry While File Download Fails
+RETRY_ASSET_DOWNLOAD_COUNT=1
+
+#Google-vision-API
+google.vision.tagging.enabled = false
+
+#Orchestrator env properties
+env="https://dev.ekstep.in/api/learning"
+
+#Current environment
+cloud_storage.env=dev
+
+
+#Folder configuration
+cloud_storage.content.folder=content
+cloud_storage.asset.folder=assets
+cloud_storage.artefact.folder=artifact
+cloud_storage.bundle.folder=bundle
+cloud_storage.media.folder=media
+cloud_storage.ecar.folder=ecar_files
+
+# Media download configuration
+content.media.base.url="https://dev.open-sunbird.org"
+plugin.media.base.url="https://dev.open-sunbird.org"
+
+# Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+# Elasticsearch properties
+search.es_conn_info="localhost:9200"
+search.fields.query=["name^100","title^100","lemma^100","code^100","tags^100","domain","subject","description^10","keywords^25","ageGroup^10","filter^10","theme^10","genre^10","objects^25","contentType^100","language^200","teachingMode^25","skills^10","learningObjective^10","curriculum^100","gradeLevel^100","developer^100","attributions^10","owner^50","text","words","releaseNotes"]
+search.fields.date=["lastUpdatedOn","createdOn","versionDate","lastSubmittedOn","lastPublishedOn"]
+search.batch.size=500
+search.connection.timeout=30
+platform-api-url="http://localhost:8080/language-service"
+MAX_ITERATION_COUNT_FOR_SAMZA_JOB=2
+
+
+# DIAL Code Configuration
+dialcode.keyspace.name="dialcode_store"
+dialcode.keyspace.table="dial_code"
+dialcode.max_count=1000
+
+# System Configuration
+system.config.keyspace.name="dialcode_store"
+system.config.table="system_config"
+
+#Publisher Configuration
+publisher.keyspace.name="dialcode_store"
+publisher.keyspace.table="publisher"
+
+#DIAL Code Generator Configuration
+dialcode.strip.chars="0"
+dialcode.length=6.0
+dialcode.large.prime_number=1679979167
+
+#DIAL Code ElasticSearch Configuration
+dialcode.index=true
+dialcode.object_type="DialCode"
+
+framework.max_term_creation_limit=200
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+# Kafka configuration details
+kafka.topics.instruction="local.learning.job.request"
+kafka.urls="localhost:9092"
+
+#Youtube Standard Licence Validation
+learning.content.youtube.validate.license=true
+learning.content.youtube.application.name=fetch-youtube-license
+youtube.license.regex.pattern=["\\?vi?=([^&]*)", "watch\\?.*v=([^&]*)", "(?:embed|vi?)/([^/?]*)","^([A-Za-z0-9\\-\\_]*)"]
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+telemetry.search.topn=5
+
+installation.id=ekstep
+
+content.copy.invalid_statusList=["Flagged","FlaggedDraft","FraggedReview","Retired", "Processing"]
+content.copy.props_to_remove=["downloadUrl", "artifactUrl", "variants",
+ "createdOn", "collections", "children", "lastUpdatedOn", "SYS_INTERNAL_LAST_UPDATED_ON",
+ "versionKey", "s3Key", "status", "pkgVersion", "toc_url", "mimeTypesCount",
+ "contentTypesCount", "leafNodesCount", "childNodes", "prevState", "lastPublishedOn",
+ "flagReasons", "compatibilityLevel", "size", "publishChecklist", "publishComment",
+ "LastPublishedBy", "rejectReasons", "rejectComment", "gradeLevel", "subject",
+ "medium", "board", "topic", "purpose", "subtopic", "contentCredits",
+ "owner", "collaborators", "creators", "contributors", "badgeAssertions", "dialcodes",
+ "concepts", "keywords", "reservedDialcodes", "dialcodeRequired", "leafNodes", "sYS_INTERNAL_LAST_UPDATED_ON", "prevStatus", "lastPublishedBy", "streamingUrl"]
+
+content.copy.origin_data=["name", "author", "license", "organisation"]
+
+
+channel.default="in.ekstep"
+
+# DialCode Link API Config
+learning.content.link_dialcode_validation=true
+dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/search"
+dialcode.api.authorization=auth_key
+
+# Language-Code Configuration
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+
+# Kafka send event to topic enable
+kafka.topic.send.enable=true
+
+learning.valid_license=["creativeCommon"]
+learning.service_provider=["youtube"]
+
+stream.mime.type=video/mp4
+compositesearch.index.name="compositesearch"
+
+hierarchy.keyspace.name=hierarchy_store
+content.hierarchy.table=content_hierarchy
+framework.hierarchy.table=framework_hierarchy
+objectcategorydefinition.keyspace=category_store
+
+# Kafka topic for definition update event.
+kafka.topic.system.command="dev.system.command"
+
+learning.reserve_dialcode.content_type=["TextBook"]
+# restrict.metadata.objectTypes=["Content", "ContentImage", "AssessmentItem", "Channel", "Framework", "Category", "CategoryInstance", "Term"]
+
+#restrict.metadata.objectTypes="Content,ContentImage"
+
+publish.collection.fullecar.disable=true
+
+# Consistency Level for Multi Node Cassandra cluster
+cassandra.lp.consistency.level=QUORUM
+
+
+
+
+content.nested.fields="badgeAssertions,targets,badgeAssociations"
+
+content.cache.ttl=86400
+content.cache.enable=true
+collection.cache.enable=true
+content.discard.status=["Draft","FlagDraft"]
+
+framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
+framework.cache.ttl=86400
+framework.cache.read=true
+
+
+# Max size(width/height) of thumbnail in pixels
+max.thumbnail.size.pixels=150
+
+collection.image.migration.enabled=true
+
+
+
+cloud_storage.upload.url.ttl=600
+
+composite.search.url="https://dev.sunbirded.org/action/composite/v3/search"
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+content.h5p.library.path="https://s3.ap-south-1.amazonaws.com/ekstep-public-prod/content/templates/h5p-library-v2.zip"
+
+kafka.topics.graph.event="sunbirddev.learning.graph.events"
+content.discard.status=["Draft","FlagDraft"]
+content.discard.remove_publish_data=["compatibilityLevel", "lastPublishedOn", "pkgVersion", "leafNodesCount", "downloadUrl", "variants"]
+
+# DIAL Link
+dial_service {
+ api {
+ base_url = "https://qa.ekstep.in/api"
+ auth_key = "auth_key"
+ }
+}
+content.link_dialcode.validation=true
+content.link_dialcode.max_limit=10
+# This is added to handle large artifacts sizes differently
+content.artifact.size.for_online=209715200
+
+# Content Import API Config
+import {
+ request_size_limit=2
+ output_topic_name="local.auto.creation.job.request"
+ required_props=["name","code","mimeType","contentType","artifactUrl","framework", "channel"]
+}
+
+contentTypeToPrimaryCategory {
+ ClassroomTeachingVideo: "Explanation Content"
+ ConceptMap: "Learning Resource"
+ Course: ["Course", "Curriculum Course", "Professional Development Course"]
+ CuriosityQuestionSet: "Practice Question Set"
+ eTextBook: "eTextbook"
+ Event: "Event"
+ EventSet: "Event Set"
+ ExperientialResource: "Learning Resource"
+ ExplanationResource: "Explanation Content"
+ ExplanationVideo: "Explanation Content"
+ FocusSpot: "Teacher Resource"
+ LearningOutcomeDefinition: "Teacher Resource"
+ MarkingSchemeRubric: "Teacher Resource"
+ PedagogyFlow: "Teacher Resource"
+ PracticeQuestionSet: "Practice Question Set"
+ PracticeResource: "Practice Question Set"
+ SelfAssess: "Course Assessment"
+ TeachingMethod: "Teacher Resource"
+ TextBook: "Digital Textbook"
+ Collection: "Content Playlist"
+ ExplanationReadingMaterial: "Learning Resource"
+ LearningActivity: "Learning Resource"
+ LessonPlan: "Content Playlist"
+ LessonPlanResource: "Teacher Resource"
+ PreviousBoardExamPapers: "Learning Resource"
+ TVLesson: "Explanation Content"
+ OnboardingResource: "Learning Resource"
+ ReadingMaterial: "Learning Resource"
+ Template: "Template"
+ Asset: "Asset"
+ Plugin: "Plugin"
+ LessonPlanUnit: "Lesson Plan Unit"
+ CourseUnit: "Course Unit"
+ TextBookUnit: "Textbook Unit"
+ Asset: "Certificate Template"
+}
+
+resourceTypeToPrimaryCategory {
+ Learn: "Learning Resource"
+ Read: "Learning Resource"
+ Practice: "Learning Resource"
+ Teach: "Teacher Resource"
+ Test: "Learning Resource"
+ Experiment: "Learning Resource"
+ LessonPlan: "Teacher Resource"
+}
+
+mimeTypeToPrimaryCategory {
+ "application/vnd.ekstep.h5p-archive": ["Learning Resource"]
+ "application/vnd.ekstep.html-archive": ["Learning Resource"]
+ "application/vnd.android.package-archive": ["Learning Resource"]
+ "video/webm": ["Explanation Content"]
+ "video/x-youtube": ["Explanation Content"]
+ "video/mp4": ["Explanation Content"]
+ "application/pdf": ["Learning Resource", "Teacher Resource"]
+ "application/epub": ["Learning Resource", "Teacher Resource"]
+ "application/vnd.ekstep.ecml-archive": ["Learning Resource", "Teacher Resource"]
+ "text/x-url": ["Learnin Resource", "Teacher Resource"]
+}
+
+#Default objectCategory mapping for channel
+
+channel {
+ content{
+ primarycategories=["Course Assessment", "Event", "eTextbook", "Explanation Content", "Learning Resource", "Practice Question Set", "Teacher Resource"]
+ additionalcategories=["Classroom Teaching Video", "Concept Map", "Curiosity Question Set", "Experiential Resource", "Explanation Video", "Focus Spot", "Learning Outcome Definition", "Lesson Plan", "Marking Scheme Rubric", "Pedagogy Flow", "Previous Board Exam Papers", "TV Lesson", "Textbook"]
+ }
+ collection {
+ primarycategories=["Content Playlist", "Course", "Digital Textbook", "Explanation Content", "Event Set"]
+ additionalcategories=["Textbook", "Lesson Plan", "TV Lesson"]
+ }
+ asset {
+ primarycategories=["Asset", "CertAsset", "Certificate Template"]
+ additionalcategories=[]
+ }
+}
+
+#config for primary categories mapping for collection units
+collection.primarycategories.mapping.enabled=true
+#config for objectType as content for collection units
+objecttype.as.content.enabled=true
diff --git a/content-api/content-service/conf/logback.xml b/content-api/content-service/conf/logback.xml
new file mode 100644
index 000000000..73529d622
--- /dev/null
+++ b/content-api/content-service/conf/logback.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ %d %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes
new file mode 100644
index 000000000..23840e8e2
--- /dev/null
+++ b/content-api/content-service/conf/routes
@@ -0,0 +1,117 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+GET /health controllers.HealthController.health
+GET /service/health controllers.HealthController.serviceHealth
+
+# Content APIs
+POST /content/v3/create controllers.v3.ContentController.create
+PATCH /content/v3/update/:identifier controllers.v3.ContentController.update(identifier:String)
+GET /content/v3/read/:identifier controllers.v3.ContentController.read(identifier:String, mode:Option[String], fields:Option[String])
+POST /content/v3/upload/url/:identifier controllers.v3.ContentController.uploadPreSigned(identifier:String, type: Option[String])
+POST /content/v3/upload/:identifier controllers.v3.ContentController.upload(identifier:String, fileFormat: Option[String], validation: Option[String])
+POST /content/v3/copy/:identifier controllers.v3.ContentController.copy(identifier:String, mode:Option[String], type:String ?= "deep")
+POST /content/v3/dialcode/link controllers.v3.ContentController.linkDialCode()
+POST /content/v3/import controllers.v3.ContentController.importContent()
+
+# Content APIs - with mock response
+POST /content/v3/flag/:identifier controllers.v3.ContentController.flag(identifier:String)
+POST /content/v3/bundle controllers.v3.ContentController.bundle
+POST /content/v3/flag/accept/:identifier controllers.v3.ContentController.acceptFlag(identifier:String)
+POST /content/v3/flag/reject/:identifier controllers.v3.ContentController.rejectFlag(identifier:String)
+POST /content/v3/publish/:identifier controllers.v3.ContentController.publish(identifier:String)
+POST /content/v3/public/publish/:identifier controllers.v3.ContentController.publish(identifier:String)
+POST /content/v3/review/:identifier controllers.v3.ContentController.review(identifier:String)
+DELETE /content/v3/discard/:identifier controllers.v3.ContentController.discard(identifier:String)
+DELETE /content/v3/retire/:identifier controllers.v3.ContentController.retire(identifier:String)
+POST /content/v3/dialcode/reserve/:identifier controllers.v3.ContentController.reserveDialCode(identifier:String)
+PATCH /content/v3/dialcode/release/:identifier controllers.v3.ContentController.releaseDialcodes(identifier:String)
+POST /content/v3/reject/:identifier controllers.v3.ContentController.rejectContent(identifier:String)
+POST /content/v3/unlisted/publish/:identifier controllers.v3.ContentController.publishUnlisted(identifier:String)
+
+# Collection APIs
+PATCH /content/v3/hierarchy/add controllers.v3.ContentController.addHierarchy
+DELETE /content/v3/hierarchy/remove controllers.v3.ContentController.removeHierarchy
+PATCH /content/v3/hierarchy/update controllers.v3.ContentController.updateHierarchy
+GET /content/v3/hierarchy/:identifier controllers.v3.ContentController.getHierarchy(identifier:String, mode:Option[String])
+GET /content/v3/hierarchy/:identifier/:bookmarkId controllers.v3.ContentController.getBookmarkHierarchy(identifier: String, bookmarkId: String, mode: Option[String])
+POST /collection/v3/dialcode/link/:identifier @controllers.v3.ContentController.collectionLinkDialCode(identifier:String)
+
+#License APIs
+POST /license/v3/create controllers.v3.LicenseController.create
+GET /license/v3/read/:identifier controllers.v3.LicenseController.read(identifier: String, fields:Option[String])
+PATCH /license/v3/update/:identifier controllers.v3.LicenseController.update(identifier: String)
+DELETE /license/v3/retire/:identifier controllers.v3.LicenseController.retire(identifier: String)
+
+#These are routes for Channel
+POST /channel/v3/create controllers.v3.ChannelController.create
+PATCH /channel/v3/update/:identifier controllers.v3.ChannelController.update(identifier: String)
+GET /channel/v3/read/:identifier controllers.v3.ChannelController.read(identifier: String)
+DELETE /channel/v3/retire/:identifier controllers.v3.ChannelController.retire(identifier: String)
+
+# Category APIs
+POST /category/v3/create controllers.v3.CategoryController.create
+GET /category/v3/read/:identifier controllers.v3.CategoryController.read(identifier: String, fields:Option[String])
+PATCH /category/v3/update/:identifier controllers.v3.CategoryController.update(identifier: String)
+DELETE /category/v3/retire/:identifier controllers.v3.CategoryController.retire(identifier: String)
+
+#Asset V4 Api's
+POST /asset/v4/create controllers.v4.AssetController.create
+PATCH /asset/v4/update/:identifier controllers.v4.AssetController.update(identifier:String)
+GET /asset/v4/read/:identifier controllers.v4.AssetController.read(identifier:String, mode:Option[String], fields:Option[String])
+POST /asset/v4/upload/:identifier controllers.v4.AssetController.upload(identifier:String, fileFormat: Option[String], validation: Option[String])
+POST /asset/v4/upload/url/:identifier controllers.v4.AssetController.uploadPreSigned(identifier:String, type: Option[String])
+POST /asset/v4/copy/:identifier controllers.v4.AssetController.copy(identifier:String)
+
+# Collection v4 Api's
+POST /collection/v4/create controllers.v4.CollectionController.create
+PATCH /collection/v4/update/:identifier controllers.v4.CollectionController.update(identifier:String)
+GET /collection/v4/read/:identifier controllers.v4.CollectionController.read(identifier:String, mode:Option[String], fields:Option[String])
+POST /collection/v4/flag/:identifier controllers.v4.CollectionController.flag(identifier:String)
+POST /collection/v4/flag/accept/:identifier controllers.v4.CollectionController.acceptFlag(identifier:String)
+DELETE /collection/v4/discard/:identifier controllers.v4.CollectionController.discard(identifier:String)
+DELETE /collection/v4/retire/:identifier controllers.v4.CollectionController.retire(identifier:String)
+PATCH /collection/v4/hierarchy/add controllers.v4.CollectionController.addHierarchy
+DELETE /collection/v4/hierarchy/remove controllers.v4.CollectionController.removeHierarchy
+PATCH /collection/v4/hierarchy/update controllers.v4.CollectionController.updateHierarchy
+GET /collection/v4/hierarchy/:identifier controllers.v4.CollectionController.getHierarchy(identifier:String, mode:Option[String])
+GET /collection/v4/hierarchy/:identifier/:bookmarkId controllers.v4.CollectionController.getBookmarkHierarchy(identifier: String, bookmarkId: String, mode: Option[String])
+POST /collection/v4/dialcode/link/:identifier controllers.v4.CollectionController.collectionLinkDialCode(identifier:String)
+POST /collection/v4/copy/:identifier controllers.v4.CollectionController.copy(identifier:String, mode:Option[String], type:String ?= "deep")
+
+
+# Content v4 APIs
+POST /content/v4/create controllers.v4.ContentController.create
+PATCH /content/v4/update/:identifier controllers.v4.ContentController.update(identifier:String)
+GET /content/v4/read/:identifier controllers.v4.ContentController.read(identifier:String, mode:Option[String], fields:Option[String])
+POST /content/v4/upload/url/:identifier controllers.v4.ContentController.uploadPreSigned(identifier:String, type: Option[String])
+POST /content/v4/upload/:identifier controllers.v4.ContentController.upload(identifier:String, fileFormat: Option[String], validation: Option[String])
+POST /content/v4/copy/:identifier controllers.v4.ContentController.copy(identifier:String, mode:Option[String], type:String ?= "deep")
+POST /content/v4/dialcode/link controllers.v4.ContentController.linkDialCode()
+POST /content/v4/import controllers.v4.ContentController.importContent()
+POST /content/v4/flag/:identifier controllers.v4.ContentController.flag(identifier:String)
+POST /content/v4/flag/accept/:identifier controllers.v4.ContentController.acceptFlag(identifier:String)
+DELETE /content/v4/discard/:identifier controllers.v4.ContentController.discard(identifier:String)
+DELETE /content/v4/retire/:identifier controllers.v4.ContentController.retire(identifier:String)
+
+# App v4 APIs
+POST /app/v4/register controllers.v4.AppController.register
+PATCH /app/v4/update/:identifier controllers.v4.AppController.update(identifier:String)
+GET /app/v4/read/:identifier controllers.v4.AppController.read(identifier:String, fields:Option[String])
+
+# Event APIs
+POST /event/v4/create controllers.v4.EventController.create
+PATCH /event/v4/update/:identifier controllers.v4.EventController.update(identifier:String)
+POST /event/v4/publish/:identifier controllers.v4.EventController.publish(identifier:String)
+GET /event/v4/read/:identifier controllers.v4.EventController.read(identifier:String, mode:Option[String], fields:Option[String])
+DELETE /event/v4/discard/:identifier controllers.v4.EventController.discard(identifier:String)
+DELETE /private/event/v4/retire/:identifier controllers.v4.EventController.retire(identifier:String)
+
+# EventSet v4 Api's
+POST /eventset/v4/create controllers.v4.EventSetController.create
+PUT /eventset/v4/update/:identifier controllers.v4.EventSetController.update(identifier:String)
+POST /eventset/v4/publish/:identifier controllers.v4.EventSetController.publish(identifier:String)
+GET /eventset/v4/hierarchy/:identifier controllers.v4.EventSetController.getHierarchy(identifier:String, mode:Option[String], fields:Option[String])
+GET /eventset/v4/read/:identifier controllers.v4.EventSetController.read(identifier:String, mode:Option[String], fields:Option[String])
+DELETE /eventset/v4/discard/:identifier controllers.v4.EventSetController.discard(identifier:String)
+DELETE /private/eventset/v4/retire/:identifier controllers.v4.EventSetController.retire(identifier:String)
\ No newline at end of file
diff --git a/content-api/content-service/pom.xml b/content-api/content-service/pom.xml
new file mode 100755
index 000000000..8a2db3be2
--- /dev/null
+++ b/content-api/content-service/pom.xml
@@ -0,0 +1,188 @@
+
+
+ 4.0.0
+ content-service
+ content-service
+ play2
+
+ org.sunbird
+ content-api
+ 1.0-SNAPSHOT
+
+
+
+ scalaz-bintray
+ Scalaz Bintray - releases
+ https://dl.bintray.com/scalaz/releases/
+
+ false
+
+
+
+
+
+ typesafe-releases-plugins
+ https://repo.typesafe.com/typesafe/releases/
+
+ false
+
+
+
+
+ 2.7.2
+ 1.0.0-rc5
+ 1.0.0
+ 2.11
+
+
+
+
+ com.google.guava
+ guava
+ 18.0
+
+
+ com.google.inject
+ guice
+ 3.0
+
+
+ com.google.inject.extensions
+ guice-assistedinject
+ 3.0
+
+
+ com.typesafe.play
+ play_${scala.major.version}
+ ${play2.version}
+
+
+ guava
+ com.google.guava
+
+
+
+
+ com.typesafe.play
+ play-guice_${scala.major.version}
+ ${play2.version}
+
+
+ guava
+ com.google.guava
+
+
+
+
+ com.typesafe.play
+ filters-helpers_${scala.major.version}
+ ${play2.version}
+
+
+ com.typesafe.play
+ play-logback_${scala.major.version}
+ ${play2.version}
+ runtime
+
+
+ com.typesafe.play
+ play-netty-server_${scala.major.version}
+ ${play2.version}
+ runtime
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.sunbird
+ content-actors
+ 1.0-SNAPSHOT
+ jar
+
+
+ slf4j-log4j12
+ org.slf4j
+
+
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ 3.1.2
+ test
+
+
+ com.typesafe.play
+ play-specs2_${scala.maj.version}
+ ${play2.version}
+ test
+
+
+ guava
+ com.google.guava
+
+
+
+
+ org.joda
+ joda-convert
+ 2.2.1
+
+
+ com.github.danielwegener
+ logback-kafka-appender
+ 0.2.0-RC2
+
+
+
+
+ ${basedir}/app
+ ${basedir}/test
+
+
+ ${basedir}/conf
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M4
+
+
+ **/*Spec.java
+ **/*Test.java
+
+
+
+
+ com.google.code.play2-maven-plugin
+ play2-maven-plugin
+ ${play2.plugin.version}
+ true
+
+
+ com.google.code.sbt-compiler-maven-plugin
+ sbt-compiler-maven-plugin
+ ${sbt-compiler.plugin.version}
+
+ -feature -deprecation -Xfatal-warnings
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+ .*RoutesPrefix.*;.*Routes.*;.*javascript.*
+
+
+
+
+
+
diff --git a/content-api/content-service/test/controllers/base/BaseSpec.scala b/content-api/content-service/test/controllers/base/BaseSpec.scala
new file mode 100644
index 000000000..24f3f4070
--- /dev/null
+++ b/content-api/content-service/test/controllers/base/BaseSpec.scala
@@ -0,0 +1,38 @@
+package controllers.base
+
+import com.typesafe.config.ConfigFactory
+import modules.TestModule
+import org.specs2.mutable.Specification
+import play.api.inject.guice.GuiceApplicationBuilder
+import play.api.libs.json.Json
+import play.api.mvc.Result
+import play.api.test.Helpers.{POST, contentAsString, contentType, defaultAwaitTimeout, route, status, _}
+import play.api.test.{FakeHeaders, FakeRequest}
+
+import scala.concurrent.Future
+
+class BaseSpec extends Specification {
+ implicit val app = new GuiceApplicationBuilder()
+ .disable(classOf[modules.ContentModule])
+ .bindings(new TestModule)
+ .build
+ implicit val config = ConfigFactory.load();
+
+ def post(apiURL: String, request: String, h: FakeHeaders = FakeHeaders(Seq()))
+ : Future[Result] = {
+ val headers = h.add(("content-type", "application/json"))
+ route(app, FakeRequest(POST, apiURL, headers, Json.toJson(Json.parse(request)))).get
+ }
+
+ def isOK(response: Future[Result]) {
+ status(response) must equalTo(OK)
+ contentType(response) must beSome.which(_ == "application/json")
+ contentAsString(response) must contain(""""status":"successful"""")
+ }
+
+ def hasClientError(response: Future[Result]) {
+ status(response) must equalTo(BAD_REQUEST)
+ contentType(response) must beSome.which(_ == "application/json")
+ contentAsString(response) must contain(""""err":"CLIENT_ERROR","status":"failed"""")
+ }
+}
diff --git a/content-api/content-service/test/controllers/v3/BadRequestSpec.scala b/content-api/content-service/test/controllers/v3/BadRequestSpec.scala
new file mode 100644
index 000000000..3eb4c7218
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/BadRequestSpec.scala
@@ -0,0 +1,19 @@
+package controllers.v3
+
+import org.junit.runner._
+import org.specs2.runner._
+
+import org.specs2.mutable.Specification
+import play.api.inject.guice.GuiceApplicationBuilder
+import play.api.test.Helpers._
+import play.api.test.FakeRequest
+
+@RunWith(classOf[JUnitRunner])
+class BadRequestSpec extends Specification {
+ implicit val app = new GuiceApplicationBuilder().build
+ "Application" should {
+ "send 404 on a bad request - /boum" in {
+ route(app, FakeRequest(GET, "/boum")) must beSome.which (status(_) == NOT_FOUND)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v3/CategorySpec.scala b/content-api/content-service/test/controllers/v3/CategorySpec.scala
new file mode 100644
index 000000000..eb972da06
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/CategorySpec.scala
@@ -0,0 +1,40 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status, _}
+
+@RunWith(classOf[JUnitRunner])
+class CategorySpec extends BaseSpec {
+
+ "Category Controller " should {
+
+ val controller = app.injector.instanceOf[controllers.v3.CategoryController]
+
+ "return success response for create API" in {
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val result = controller.read("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for retire API" in {
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v3/ChannelSpec.scala b/content-api/content-service/test/controllers/v3/ChannelSpec.scala
new file mode 100644
index 000000000..cc9f93db6
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/ChannelSpec.scala
@@ -0,0 +1,44 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+
+@RunWith(classOf[JUnitRunner])
+class ChannelSpec extends BaseSpec {
+ "Channel Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ChannelController]
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ChannelController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ChannelController]
+ val result = controller.read("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for retire API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ChannelController]
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+}
+
+
+
diff --git a/content-api/content-service/test/controllers/v3/ContentSpec.scala b/content-api/content-service/test/controllers/v3/ContentSpec.scala
new file mode 100644
index 000000000..40a050f93
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/ContentSpec.scala
@@ -0,0 +1,223 @@
+package controllers.v3
+
+import java.io.File
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import org.sunbird.models.UploadParams
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+import play.api.libs.Files.{SingletonTemporaryFileCreator, TemporaryFile}
+import play.api.libs.json.JsValue
+import play.api.mvc.MultipartFormData
+import play.api.mvc.MultipartFormData.{BadPart, FilePart}
+import play.api.libs.json.Json
+
+@RunWith(classOf[JUnitRunner])
+class ContentSpec extends BaseSpec {
+
+ "Content Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": {"contentType": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v3/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy add API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.addHierarchy()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy remove API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.removeHierarchy()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy get API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.getHierarchy("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for flag API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.flag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for acceptFlag API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.acceptFlag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for rejectFlag API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.rejectFlag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for bundle API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.bundle()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for publish API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.publish("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for review API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.review("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for discard API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.discard("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for retire API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.retire("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for linkDialCode API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.linkDialCode()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for collectionLinkDialCode API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.collectionLinkDialCode("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for reserveDialCode API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.reserveDialCode("01234")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for releaseDialcodes API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.releaseDialcodes("01234")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for rejectContent API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.rejectContent("01234")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for publishUnlisted API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.publishUnlisted("01234")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for presignedUrl upload API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.uploadPreSigned("01234", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+ "return success response for upload API with file" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]]("fileUrl" -> Seq("https://abc.com/content/sample.pdf"), "filePath" -> Seq("/program/id")), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl and fileFormat" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", Some("composed-h5p-zip"), None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for importContent API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.importContent()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy update API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"data": {"mimeType": "application/vnd.ekstep.content-collection"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v3/hierarchy/update").withJsonBody(json)
+ val result = controller.updateHierarchy()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for bookmark hierarchy API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val result = controller.getBookmarkHierarchy("do_123", "do_1234", Option.apply("read"))(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for copy API" in {
+ val controller = app.injector.instanceOf[controllers.v3.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": {"primaryCategory": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v3/copy/do_123").withJsonBody(json)
+ val result = controller.copy("do_123", Option.apply("read"), "shallowCopy")(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+}
diff --git a/content-api/content-service/test/controllers/v3/HealthSpec.scala b/content-api/content-service/test/controllers/v3/HealthSpec.scala
new file mode 100644
index 000000000..d8d7b0430
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/HealthSpec.scala
@@ -0,0 +1,19 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner._
+import org.specs2.runner._
+import play.api.test.Helpers._
+import play.api.test._
+
+@RunWith(classOf[JUnitRunner])
+class HealthSpec extends BaseSpec {
+ "Application" should {
+ "return api health status report - successful" in {
+ val controller = app.injector.instanceOf[controllers.HealthController]
+ val result = controller.health()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v3/LicenseSpec.scala b/content-api/content-service/test/controllers/v3/LicenseSpec.scala
new file mode 100644
index 000000000..926adfadd
--- /dev/null
+++ b/content-api/content-service/test/controllers/v3/LicenseSpec.scala
@@ -0,0 +1,41 @@
+package controllers.v3
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+
+@RunWith(classOf[JUnitRunner])
+class LicenseSpec extends BaseSpec {
+
+ "License Controller " should {
+
+ val controller = app.injector.instanceOf[controllers.v3.LicenseController]
+
+ "return success response for create API" in {
+ val result = controller.create()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val result = controller.read("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for retire API" in {
+ val result = controller.retire("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v4/AppSpec.scala b/content-api/content-service/test/controllers/v4/AppSpec.scala
new file mode 100644
index 000000000..74ea948fe
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/AppSpec.scala
@@ -0,0 +1,39 @@
+package controllers.v4
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+import play.api.libs.json.JsValue
+import play.api.libs.json.Json
+
+@RunWith(classOf[JUnitRunner])
+class AppSpec extends BaseSpec {
+
+ "AppController " should {
+ "return success response for register API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AppController]
+ val json: JsValue = Json.parse("""{"request":{"app":{"name":"Test Integration App","description":"Description of Test Integration App","provider":{"name":"Test Organisation","copyright":"CC BY 4.0"},"osType":"android","osMetadata":{"packageId":"org.test.integration","appVersion":"1.0","compatibilityVer":"1.0"},"appTarget":{"mimeType":["application/pdf"]}}}}""")
+ val fakeRequest = FakeRequest("POST", "/app/v4/register").withJsonBody(json)
+ val result = controller.register()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AppController]
+ val result = controller.update("android-org.test.integration")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AppController]
+ val result = controller.read("android-org.test.integration", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v4/AssetSpec.scala b/content-api/content-service/test/controllers/v4/AssetSpec.scala
new file mode 100644
index 000000000..3f4692e3f
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/AssetSpec.scala
@@ -0,0 +1,114 @@
+package controllers.v4
+
+import java.io.File
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+import play.api.libs.Files.{SingletonTemporaryFileCreator, TemporaryFile}
+import play.api.libs.json.JsValue
+import play.api.mvc.MultipartFormData
+import play.api.mvc.MultipartFormData.{BadPart, FilePart}
+import play.api.libs.json.Json
+
+@RunWith(classOf[JUnitRunner])
+class AssetSpec extends BaseSpec {
+
+ "AssetController " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val json: JsValue = Json.parse("""{"request": {"asset": { "primaryCategory": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/asset/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with file" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]]("fileUrl" -> Seq("https://abc.com/content/sample.pdf"), "filePath" -> Seq("/program/id")), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl and fileFormat" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", Some("composed-h5p-zip"), None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for pre signed Url upload API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val result = controller.uploadPreSigned("01234", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for copy API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val json: JsValue = Json.parse("""{"request": {"asset": { "name": "Asset-Test"}}}""")
+ val fakeRequest = FakeRequest("POST", "/asset/v4/copy ").withJsonBody(json)
+ val result = controller.copy("01234")(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+
+ "Asset controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val json: JsValue = Json.parse("""{"request": {"asset": { "contentType": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/asset/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+
+ "Asset controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.AssetController]
+ val json: JsValue = Json.parse("""{"request": {"asset": { "name": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/asset/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v4/CollectionSpec.scala b/content-api/content-service/test/controllers/v4/CollectionSpec.scala
new file mode 100644
index 000000000..6f570eae5
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/CollectionSpec.scala
@@ -0,0 +1,138 @@
+package controllers.v4
+
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+import play.api.libs.json.JsValue
+import play.api.libs.json.Json
+
+@RunWith(classOf[JUnitRunner])
+class CollectionSpec extends BaseSpec {
+
+ "Collection Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val json: JsValue = Json.parse("""{"request": {"collection": {"name": "Collection","primaryCategory": "Digital Textbook"}}}""")
+ val fakeRequest = FakeRequest("POST", "/collection/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy add API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.addHierarchy()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy remove API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.removeHierarchy()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy get API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.getHierarchy("do_123", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for flag API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.flag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for acceptFlag API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.acceptFlag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for discard API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.discard("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for retire API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.retire("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for collectionLinkDialCode API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.collectionLinkDialCode("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+
+ "return success response for hierarchy update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val json: JsValue = Json.parse("""{"request": {"data": {"mimeType": "application/vnd.ekstep.content-collection"}}}""")
+ val fakeRequest = FakeRequest("POST", "/collection/v4/hierarchy/update").withJsonBody(json)
+ val result = controller.updateHierarchy()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for bookmark hierarchy API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val result = controller.getBookmarkHierarchy("do_123", "do_1234", Option.apply("read"))(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for copy API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val json: JsValue = Json.parse("""{"request": {"collection": {"primaryCategory": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/collection/v4/copy/do_123").withJsonBody(json)
+ val result = controller.copy("do_123", Option.apply("read"), "shallowCopy")(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "Collection Controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val json: JsValue = Json.parse("""{"request": {"collection": { "contentType": "TextBook"}}}""")
+ val fakeRequest = FakeRequest("POST", "/collection/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+
+ "Collection Controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.CollectionController]
+ val json: JsValue = Json.parse("""{"request": {"collection": { "name": "Textbook"}}}""")
+ val fakeRequest = FakeRequest("POST", "/collection/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v4/ContentSpec.scala b/content-api/content-service/test/controllers/v4/ContentSpec.scala
new file mode 100644
index 000000000..98d4deb8d
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/ContentSpec.scala
@@ -0,0 +1,154 @@
+package controllers.v4
+
+import java.io.File
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status}
+import play.api.test.Helpers._
+import play.api.libs.Files.{SingletonTemporaryFileCreator, TemporaryFile}
+import play.api.libs.json.JsValue
+import play.api.mvc.MultipartFormData
+import play.api.mvc.MultipartFormData.{BadPart, FilePart}
+import play.api.libs.json.Json
+
+@RunWith(classOf[JUnitRunner])
+class ContentSpec extends BaseSpec {
+
+ "Content Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": {"primaryCategory": "Learning Resource"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.read("do_123", None, None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for flag API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.flag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for acceptFlag API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.acceptFlag("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for discard API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.discard("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for retire API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.retire("0123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for linkDialCode API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.linkDialCode()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for presignedUrl upload API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.uploadPreSigned("01234", None)(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ }
+ "return success response for upload API with file" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]]("fileUrl" -> Seq("https://abc.com/content/sample.pdf"), "filePath" -> Seq("/program/id")), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", None, None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for upload API with fileUrl and fileFormat" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val file = new File("test/resources/sample.pdf")
+ val files = Seq[FilePart[TemporaryFile]](FilePart("file", "sample.pdf", None, SingletonTemporaryFileCreator.create(file.toPath)))
+ val multipartBody = MultipartFormData(Map[String, Seq[String]](), files, Seq[BadPart]())
+ val fakeRequest = FakeRequest().withMultipartFormDataBody(multipartBody)
+ val result = controller.upload("01234", Some("composed-h5p-zip"), None)(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for importContent API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val result = controller.importContent()(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+
+ "return success response for copy API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": {"primaryCategory": "Asset"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v4/copy/do_123").withJsonBody(json)
+ val result = controller.copy("do_123", Option.apply("read"), "shallowCopy")(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "Content Controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": { "contentType": "TextBook"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+
+ "Content Controller with invalid request " should {
+ "return client error response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.ContentController]
+ val json: JsValue = Json.parse("""{"request": {"content": { "name": "Resource"}}}""")
+ val fakeRequest = FakeRequest("POST", "/content/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+ }
+}
diff --git a/content-api/content-service/test/controllers/v4/EventSetSpec.scala b/content-api/content-service/test/controllers/v4/EventSetSpec.scala
new file mode 100644
index 000000000..2fcbcf0ed
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/EventSetSpec.scala
@@ -0,0 +1,75 @@
+package controllers.v4
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.libs.json.{JsValue, Json}
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status, _}
+
+@RunWith(classOf[JUnitRunner])
+class EventSetSpec extends BaseSpec {
+
+ "EventSet Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val json: JsValue = Json.parse("""{"request": {"eventset": {"name": "EventSet","primaryCategory": "Event Set"}}}""")
+ val fakeRequest = FakeRequest("POST", "/eventset/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.update("do_123")(FakeRequest("POST", "/eventset/v4/update "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.read("do_123", None, None)(FakeRequest("POST", "/eventset/v4/read "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for hierarchy get API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.getHierarchy("do_123", None, None)(FakeRequest("POST", "/eventset/v4/hierarchy "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for discard API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.discard("0123")(FakeRequest("POST", "/eventset/v4/discard "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+ "return success response for retire API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.retire("0123")(FakeRequest("POST", "/eventset/v4/retire "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return error response when updating status using update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val json: JsValue = Json.parse("""{"request": {"eventset": {"status": "Live"}}}""")
+ val fakeRequest = FakeRequest("POST", "/eventset/v4/update ").withJsonBody(json)
+ val result = controller.update("do_123")(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+
+ "return success response for publish API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventSetController]
+ val result = controller.publish("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/content-api/content-service/test/controllers/v4/EventSpec.scala b/content-api/content-service/test/controllers/v4/EventSpec.scala
new file mode 100644
index 000000000..e184fd701
--- /dev/null
+++ b/content-api/content-service/test/controllers/v4/EventSpec.scala
@@ -0,0 +1,54 @@
+package controllers.v4
+
+import controllers.base.BaseSpec
+import org.junit.runner.RunWith
+import org.specs2.runner.JUnitRunner
+import play.api.libs.json.{JsValue, Json}
+import play.api.test.FakeRequest
+import play.api.test.Helpers.{OK, status, _}
+
+@RunWith(classOf[JUnitRunner])
+class EventSpec extends BaseSpec {
+
+ "Event Controller " should {
+ "return success response for create API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventController]
+ val json: JsValue = Json.parse("""{"request": {"event": {"name": "Event","primaryCategory": "Event"}}}""")
+ val fakeRequest = FakeRequest("POST", "/event/v4/create ").withJsonBody(json)
+ val result = controller.create()(fakeRequest)
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for read API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventController]
+ val result = controller.read("do_123", None, None)(FakeRequest("POST", "/event/v4/read "))
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return success response for update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventController]
+ val result = controller.update("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ "return error response when updating status using update API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventController]
+ val json: JsValue = Json.parse("""{"request": {"event": {"status": "Live"}}}""")
+ val fakeRequest = FakeRequest("POST", "/event/v4/update ").withJsonBody(json)
+ val result = controller.update("do_123")(fakeRequest)
+ status(result) must equalTo(BAD_REQUEST)
+ }
+
+ "return success response for publish API" in {
+ val controller = app.injector.instanceOf[controllers.v4.EventController]
+ val result = controller.publish("do_123")(FakeRequest())
+ isOK(result)
+ status(result) must equalTo(OK)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/content-api/content-service/test/modules/TestModule.scala b/content-api/content-service/test/modules/TestModule.scala
new file mode 100644
index 000000000..938b737e2
--- /dev/null
+++ b/content-api/content-service/test/modules/TestModule.scala
@@ -0,0 +1,35 @@
+package modules
+
+import com.google.inject.AbstractModule
+import org.sunbird.actor.core.BaseActor
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import play.libs.akka.AkkaGuiceSupport
+import utils.ActorNames
+
+import scala.concurrent.{ExecutionContext, Future}
+
+class TestModule extends AbstractModule with AkkaGuiceSupport {
+
+ override def configure(): Unit = {
+ bindActor(classOf[TestActor], ActorNames.HEALTH_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.CONTENT_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.LICENSE_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.COLLECTION_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.CHANNEL_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.CATEGORY_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.ASSET_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.APP_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.EVENT_SET_ACTOR)
+ bindActor(classOf[TestActor], ActorNames.EVENT_ACTOR)
+ println("Test Module is initialized...")
+ }
+}
+
+class TestActor extends BaseActor {
+
+ implicit val ec: ExecutionContext = getContext().dispatcher
+
+ override def onReceive(request: Request): Future[Response] = {
+ Future(ResponseHandler.OK)
+ }
+}
diff --git a/content-api/content-service/test/resources/sample.pdf b/content-api/content-service/test/resources/sample.pdf
new file mode 100644
index 000000000..dbf091df9
Binary files /dev/null and b/content-api/content-service/test/resources/sample.pdf differ
diff --git a/content-api/hierarchy-manager/pom.xml b/content-api/hierarchy-manager/pom.xml
new file mode 100644
index 000000000..828dac9fc
--- /dev/null
+++ b/content-api/hierarchy-manager/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ content-api
+ org.sunbird
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ hierarchy-manager
+
+
+
+ org.sunbird
+ graph-engine_2.11
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.sunbird
+ platform-common
+ 1.0-SNAPSHOT
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.scalatest
+ scalatest_${scala.maj.version}
+ 3.1.2
+ test
+
+
+ org.neo4j
+ neo4j-bolt
+ 3.5.0
+ test
+
+
+ org.neo4j
+ neo4j-graphdb-api
+ 3.5.0
+ test
+
+
+ org.neo4j
+ neo4j
+ 3.5.0
+ test
+
+
+ org.cassandraunit
+ cassandra-unit
+ 3.11.2.0
+ test
+
+
+ httpcore
+ org.apache.httpcomponents
+
+
+ httpclient
+ org.apache.httpcomponents
+
+
+
+
+ com.mashape.unirest
+ unirest-java
+ 1.4.9
+
+
+
+
+
+ src/main/scala
+ src/test/scala
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.4.0
+
+ ${scala.version}
+ false
+
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+ scala-test-compile
+ process-test-resources
+
+ testCompile
+
+
+
+
+
+ org.scalatest
+ scalatest-maven-plugin
+ 2.0.0
+
+
+ test
+ test
+
+ test
+
+
+
+
+
+ org.scoverage
+ scoverage-maven-plugin
+ ${scoverage.plugin.version}
+
+ ${scala.version}
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
new file mode 100644
index 000000000..86e0efda8
--- /dev/null
+++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
@@ -0,0 +1,618 @@
+package org.sunbird.managers
+
+import java.util
+import java.util.concurrent.CompletionException
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException}
+import org.sunbird.common.{JsonUtils, Platform, Slug}
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters
+import scala.collection.JavaConverters.asJavaIterableConverter
+import scala.concurrent.{ExecutionContext, Future}
+import com.mashape.unirest.http.HttpResponse
+import com.mashape.unirest.http.Unirest
+import org.apache.commons.collections4.{CollectionUtils, MapUtils}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.schema.DefinitionNode
+import org.sunbird.utils.{HierarchyBackwardCompatibilityUtil, HierarchyConstants, HierarchyErrorCodes}
+
+object HierarchyManager {
+
+ val schemaName: String = "collection"
+ val schemaVersion: String = "1.0"
+ val imgSuffix: String = ".img"
+ val hierarchyPrefix: String = "hierarchy_"
+ val statusList = List("Live", "Unlisted", "Flagged")
+
+ val keyTobeRemoved = {
+ if(Platform.config.hasPath("content.hierarchy.removed_props_for_leafNodes"))
+ Platform.config.getStringList("content.hierarchy.removed_props_for_leafNodes")
+ else
+ java.util.Arrays.asList("collections","children","usedByContent","item_sets","methods","libraries","editorState")
+ }
+
+ val mapPrimaryCategoriesEnabled: Boolean = if (Platform.config.hasPath("collection.primarycategories.mapping.enabled")) Platform.config.getBoolean("collection.primarycategories.mapping.enabled") else true
+ val objectTypeAsContentEnabled: Boolean = if (Platform.config.hasPath("objecttype.as.content.enabled")) Platform.config.getBoolean("objecttype.as.content.enabled") else true
+
+ @throws[Exception]
+ def addLeafNodesToHierarchy(request:Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateRequest(request)
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ val unitId = request.get("unitId").asInstanceOf[String]
+ val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes", "originData"), schemaName, schemaVersion)
+ validateShallowCopied(rootNodeMap, "add", rootNode.getIdentifier.replaceAll(imgSuffix, ""))
+ if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
+ Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "unitId " + unitId + " does not exist")}
+ }else {
+ val hierarchyFuture = fetchHierarchy(request, rootNode.getIdentifier)
+ hierarchyFuture.map(hierarchy => {
+ if(hierarchy.isEmpty){
+ Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
+ } else {
+ val leafNodesFuture = fetchLeafNodes(request)
+ leafNodesFuture.map(leafNodes => {
+ updateRootNode(rootNode, request, "add").map(node => {
+ val updateResponse = updateHierarchy(unitId, hierarchy, leafNodes, node, request, "add")
+ updateResponse.map(response => {
+ if(!ResponseHandler.checkError(response)) {
+ ResponseHandler.OK
+ .put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))
+ .put(unitId, request.get("children"))
+ }else {
+ response
+ }
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
+ }
+
+ @throws[Exception]
+ def removeLeafNodesFromHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateRequest(request)
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ val unitId = request.get("unitId").asInstanceOf[String]
+ val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes", "originData"), schemaName, schemaVersion)
+ validateShallowCopied(rootNodeMap, "remove", rootNode.getIdentifier.replaceAll(imgSuffix, ""))
+ if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
+ Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "unitId " + unitId + " does not exist")}
+ }else {
+ val hierarchyFuture = fetchHierarchy(request, rootNode.getIdentifier)
+ hierarchyFuture.map(hierarchy => {
+ if(hierarchy.isEmpty){
+ Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
+ } else {
+ updateRootNode(rootNode, request, "remove").map(node =>{
+ val updateResponse = updateHierarchy(unitId, hierarchy, null, node, request, "remove")
+ updateResponse.map(response => {
+ if(!ResponseHandler.checkError(response)) {
+ ResponseHandler.OK.put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))
+ } else {
+ response
+ }
+ })
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
+ }
+
+ @throws[Exception]
+ def getHierarchy(request : Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val mode = request.get("mode").asInstanceOf[String]
+ if(StringUtils.isNotEmpty(mode) && mode.equals("edit"))
+ getUnPublishedHierarchy(request)
+ else
+ getPublishedHierarchy(request)
+ }
+
+ @throws[Exception]
+ def getUnPublishedHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ val rootNodeFuture = getRootNode(request)
+ rootNodeFuture.map(rootNode => {
+ if (StringUtils.equalsIgnoreCase("Retired", rootNode.getMetadata.getOrDefault("status", "").asInstanceOf[String])) {
+ Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist"))
+ }
+ val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
+ var metadata: util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ val hierarchy = fetchHierarchy(request, rootNode.getIdentifier)
+ //TODO: Remove content Mapping for backward compatibility
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(metadata)
+ hierarchy.map(hierarchy => {
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]]
+ //TODO: Remove content Mapping for backward compatibility
+ updateContentMappingInChildren(children)
+ val leafNodeIds = new util.ArrayList[String]()
+ fetchAllLeafNodes(children, leafNodeIds)
+ getLatestLeafNodes(leafNodeIds).map(leafNodesMap => {
+ updateLatestLeafNodes(children, leafNodesMap)
+ metadata.put("children", children)
+ metadata.put("identifier", request.get("rootId"))
+ if(StringUtils.isNotEmpty(bookmarkId))
+ metadata = filterBookmarkHierarchy(metadata.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]], bookmarkId)
+ if (MapUtils.isEmpty(metadata)) {
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
+ } else {
+ ResponseHandler.OK.put("content", metadata)
+ }
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f) recoverWith { case e: ResourceNotFoundException => {
+ val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String])
+ searchResponse.map(rootHierarchy => {
+ if(!rootHierarchy.isEmpty && StringUtils.isNotEmpty(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])){
+ //TODO: Remove content Mapping for backward compatibility
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]])
+ val unPublishedBookmarkHierarchy = getUnpublishedBookmarkHierarchy(request, rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])
+ unPublishedBookmarkHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]]
+ //TODO: Remove content Mapping for backward compatibility
+ updateContentMappingInChildren(children)
+ val leafNodeIds = new util.ArrayList[String]()
+ fetchAllLeafNodes(children, leafNodeIds)
+ getLatestLeafNodes(leafNodeIds).map(leafNodesMap => {
+ updateLatestLeafNodes(children, leafNodesMap)
+ hierarchy.put("children", children)
+ })
+ ResponseHandler.OK.put("content", hierarchy)
+ } else
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")
+ })
+ } else {
+ Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist"))
+ }
+ }).flatMap(f => f)
+ }
+ }
+ }
+
+ @throws[Exception]
+ def getPublishedHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ val redisHierarchy = RedisCache.get(hierarchyPrefix + request.get("rootId"))
+ val hierarchyFuture = if (StringUtils.isNotEmpty(redisHierarchy)) {
+ Future(mapAsJavaMap(Map("content" -> JsonUtils.deserialize(redisHierarchy, classOf[java.util.Map[String, AnyRef]]))))
+ } else getCassandraHierarchy(request)
+ hierarchyFuture.map(result => {
+ if (!result.isEmpty) {
+ val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
+ val rootHierarchy = result.get("content").asInstanceOf[util.Map[String, AnyRef]]
+ if (StringUtils.isEmpty(bookmarkId)) {
+ ResponseHandler.OK.put("content", rootHierarchy)
+ } else {
+ val children = rootHierarchy.getOrElse("children", new util.ArrayList[util.Map[String, AnyRef]]()).asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ val bookmarkHierarchy = filterBookmarkHierarchy(children, bookmarkId)
+ if (MapUtils.isEmpty(bookmarkHierarchy)) {
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
+ } else {
+ ResponseHandler.OK.put("content", bookmarkHierarchy)
+ }
+ }
+ } else
+ ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")
+ })
+ }
+
+ def validateRequest(request: Request)(implicit ec: ExecutionContext) = {
+ val rootId = request.get("rootId").asInstanceOf[String]
+ val unitId = request.get("unitId").asInstanceOf[String]
+ val children = request.get("children").asInstanceOf[java.util.List[String]]
+
+ if (StringUtils.isBlank(rootId)) {
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "rootId is mandatory")
+ }
+ if (StringUtils.isBlank(unitId)) {
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "unitId is mandatory")
+ }
+ if (null == children || children.isEmpty) {
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "children are mandatory")
+ }
+ }
+
+ private def getRootNode(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val req = new Request(request)
+ req.put("identifier", request.get("rootId").asInstanceOf[String])
+ req.put("mode", request.get("mode").asInstanceOf[String])
+ req.put("fields",request.get("fields").asInstanceOf[java.util.List[String]])
+ DataNode.read(req)
+ }
+
+ def fetchLeafNodes(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
+ val req = new Request(request)
+ req.put("identifiers", leafNodes)
+ val nodes = DataNode.list(req).map(nodes => {
+ if(nodes.size() != leafNodes.size()) {
+ val filteredList = leafNodes.toList.filter(id => !nodes.contains(id))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Children which are not available are: " + leafNodes)
+ }
+ else nodes.toList
+ })
+ nodes
+ }
+
+ def convertNodeToMap(leafNodes: List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): java.util.List[java.util.Map[String, AnyRef]] = {
+ leafNodes.map(node => {
+ val nodeMap:java.util.Map[String,AnyRef] = NodeUtil.serialize(node, null, node.getObjectType.toLowerCase().replace("image", ""), schemaVersion)
+ nodeMap.keySet().removeAll(keyTobeRemoved)
+ nodeMap
+ })
+ }
+
+ def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Unit = {
+ val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
+ if(null != childNodes && !childNodes.isEmpty){
+ val child = childNodes.get(0)
+ leafNodes.toList.map(leafNode => validateLeafNodes(child, leafNode))
+ val childList = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]]
+ val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, (child.get("depth").asInstanceOf[Integer] + 1), unitId)
+ child.put("children", restructuredChildren)
+ } else {
+ for(child <- children) {
+ if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
+ addChildrenToUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodes, leafNodeIds)
+ }
+ }
+ }
+
+ def removeChildrenFromUnit(children: java.util.List[java.util.Map[String, AnyRef]], unitId: String, leafNodeIds: java.util.List[String]):Unit = {
+ val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
+ if(null != childNodes && !childNodes.isEmpty){
+ val child = childNodes.get(0)
+ if(null != child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) {
+ var filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].filter(existingLeafNode => {
+ !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
+ })
+ var index: Integer = 1
+ filteredLeafNodes.toList.sortBy(x => x.get("index").asInstanceOf[Integer]).foreach(node => {
+ node.put("index", index)
+ index += 1
+ })
+ child.put("children", filteredLeafNodes)
+ }
+ } else {
+ for(child <- children) {
+ if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
+ removeChildrenFromUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodeIds)
+ }
+ }
+ }
+
+ def updateRootNode(rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val req = new Request(request)
+ val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
+ var childNodes = new java.util.ArrayList[String]()
+ childNodes.addAll(rootNode.getMetadata.get("childNodes").asInstanceOf[Array[String]].toList)
+ if(operation.equalsIgnoreCase("add"))
+ childNodes.addAll(leafNodes)
+ if(operation.equalsIgnoreCase("remove"))
+ childNodes.removeAll(leafNodes)
+ req.put("childNodes", childNodes.distinct.toArray)
+ req.getContext.put("identifier", rootNode.getIdentifier.replaceAll(imgSuffix, ""))
+ req.getContext.put("skipValidation", java.lang.Boolean.TRUE)
+ DataNode.update(req)
+ }
+
+ def updateHierarchy(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]]
+ if("add".equalsIgnoreCase(operation)){
+ val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes)
+ addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds)
+ }
+ if("remove".equalsIgnoreCase(operation)) {
+ removeChildrenFromUnit(children,unitId, leafNodeIds)
+ }
+ val rootId = rootNode.getIdentifier.replaceAll(imgSuffix, "")
+ val updatedHierarchy = new java.util.HashMap[String, AnyRef]()
+ updatedHierarchy.put("identifier", rootId)
+ updatedHierarchy.put("children", children)
+ val req = new Request(request)
+ req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy))
+ req.put("identifier", rootNode.getIdentifier)
+ oec.graphService.saveExternalProps(req)
+ }
+
+ def restructureUnit(childList: java.util.List[java.util.Map[String, AnyRef]], leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], depth: Integer, parent: String): java.util.List[java.util.Map[String, AnyRef]] = {
+ var maxIndex:Integer = 0
+ var leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]()
+ for(leafNode <- leafNodes){
+ leafNodeMap.put(leafNode.get("identifier").asInstanceOf[String], JavaConverters.mapAsJavaMapConverter(leafNode).asJava)
+ }
+ var filteredLeafNodes: java.util.List[java.util.Map[String, AnyRef]] = new util.ArrayList[java.util.Map[String, AnyRef]]()
+ if(null != childList && !childList.isEmpty) {
+ val childMap:Map[String, java.util.Map[String, AnyRef]] = childList.toList.map(f => f.get("identifier").asInstanceOf[String] -> f).toMap
+ val existingLeafNodes = childMap.filter(p => leafNodeIds.contains(p._1))
+ existingLeafNodes.map(en => {
+ leafNodeMap.get(en._1).put("index", en._2.get("index").asInstanceOf[Integer])
+ })
+ filteredLeafNodes = bufferAsJavaList(childList.filter(existingLeafNode => {
+ !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
+ }))
+ maxIndex = childMap.values.toList.map(child => child.get("index").asInstanceOf[Integer]).toList.max.asInstanceOf[Integer]
+ }
+ leafNodeIds.foreach(id => {
+ var node = leafNodeMap.get(id)
+ node.put("parent", parent)
+ node.put("depth", depth)
+ if( null == node.get("index")) {
+ val index:Integer = maxIndex + 1
+ node.put("index", index)
+ maxIndex += 1
+ }
+ filteredLeafNodes.add(node)
+ })
+ filteredLeafNodes
+ }
+
+ def fetchHierarchy(request: Request, identifier: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Map[String, AnyRef]] = {
+ val req = new Request(request)
+ req.put("identifier", identifier)
+ val responseFuture = oec.graphService.readExternalProps(req, List("hierarchy"))
+ responseFuture.map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ val hierarchyString = response.getResult.toMap.getOrDefault("hierarchy", "").asInstanceOf[String]
+ if (StringUtils.isNotEmpty(hierarchyString)) {
+ Future(JsonUtils.deserialize(hierarchyString, classOf[java.util.Map[String, AnyRef]]).toMap)
+ } else
+ Future(Map[String, AnyRef]())
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404 && Platform.config.hasPath("collection.image.migration.enabled") && Platform.config.getBoolean("collection.image.migration.enabled")) {
+ req.put("identifier", identifier.replaceAll(".img", "") + ".img")
+ val responseFuture = oec.graphService.readExternalProps(req, List("hierarchy"))
+ responseFuture.map(response => {
+ if (!ResponseHandler.checkError(response)) {
+ val hierarchyString = response.getResult.toMap.getOrDefault("hierarchy", "").asInstanceOf[String]
+ if (StringUtils.isNotEmpty(hierarchyString)) {
+ JsonUtils.deserialize(hierarchyString, classOf[java.util.Map[String, AnyRef]]).toMap
+ } else
+ Map[String, AnyRef]()
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404)
+ Map[String, AnyRef]()
+ else
+ throw new ServerException("ERR_WHILE_FETCHING_HIERARCHY_FROM_CASSANDRA", "Error while fetching hierarchy from cassandra")
+ })
+ } else if (ResponseHandler.checkError(response) && response.getResponseCode.code() == 404)
+ Future(Map[String, AnyRef]())
+ else
+ throw new ServerException("ERR_WHILE_FETCHING_HIERARCHY_FROM_CASSANDRA", "Error while fetching hierarchy from cassandra")
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def getCassandraHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[util.Map[String, AnyRef]] = {
+ val rootHierarchy: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]()
+ val hierarchy = fetchHierarchy(request, request.getRequest.get("rootId").asInstanceOf[String])
+ hierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ if (StringUtils.isNotEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String])) {
+ //TODO: Remove mapping
+ val hierarchyMap = mapPrimaryCategories(hierarchy)
+ rootHierarchy.put("content", hierarchyMap)
+ RedisCache.set(hierarchyPrefix + request.get("rootId"), JsonUtils.serialize(hierarchyMap))
+ Future(rootHierarchy)
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String])
+ searchResponse.map(response => {
+ if (!response.isEmpty) {
+ if (StringUtils.isNotEmpty(response.getOrDefault("identifier", "").asInstanceOf[String])) {
+ val parentHierarchy = fetchHierarchy(request, response.get("identifier").asInstanceOf[String])
+ parentHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty) {
+ if (StringUtils.isNoneEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.HashMap[String, AnyRef]]])) {
+ val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String])
+ if (!bookmarkHierarchy.isEmpty) {
+ //TODO: Remove mapping
+ val hierarchyMap = mapPrimaryCategories(bookmarkHierarchy)
+ rootHierarchy.put("content", hierarchyMap)
+ RedisCache.set(hierarchyPrefix + request.get("rootId"), JsonUtils.serialize(hierarchyMap))
+ rootHierarchy
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ })
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ }).flatMap(f => f)
+ }
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ def searchRootIdInElasticSearch(rootId: String)(implicit ec: ExecutionContext): Future[util.Map[String, AnyRef]] = {
+ val mapper: ObjectMapper = new ObjectMapper()
+ val searchRequest: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]() {
+ put("request", new util.HashMap[String, AnyRef]() {
+ put("filters", new util.HashMap[String, AnyRef]() {
+ put("status", new util.ArrayList[String]() {
+ add("Live");
+ add("Unlisted")
+ })
+ put("mimeType", "application/vnd.ekstep.content-collection")
+ put("childNodes", new util.ArrayList[String]() {
+ add(rootId)
+ })
+ put("visibility", "Default")
+ })
+ put("fields", new util.ArrayList[String]() {
+ add("identifier")
+ })
+ })
+ }
+ val url: String = if (Platform.config.hasPath("composite.search.url")) Platform.config.getString("composite.search.url") else "https://dev.sunbirded.org/action/composite/v3/search"
+ val httpResponse: HttpResponse[String] = Unirest.post(url).header("Content-Type", "application/json").body(mapper.writeValueAsString(searchRequest)).asString
+ if (httpResponse.getStatus == 200) {
+ val response: Response = JsonUtils.deserialize(httpResponse.getBody, classOf[Response])
+ if (response.get("count").asInstanceOf[Integer] > 0 && CollectionUtils.isNotEmpty(response.get("content").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) {
+ Future(response.get("content").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]].get(0))
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ } else {
+ throw new ServerException("SERVER_ERROR", "Invalid response from search")
+ }
+ }
+
+ def filterBookmarkHierarchy(children: util.List[util.Map[String, AnyRef]], bookmarkId: String)(implicit ec: ExecutionContext): util.Map[String, AnyRef] = {
+ if (CollectionUtils.isNotEmpty(children)) {
+ val response = children.filter(_.get("identifier") == bookmarkId).toList
+ if (CollectionUtils.isNotEmpty(response)) {
+ response.get(0)
+ } else {
+ val nextChildren = bufferAsJavaList(children.flatMap(child => {
+ if (!child.isEmpty && CollectionUtils.isNotEmpty(child.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]))
+ child.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ else new util.ArrayList[util.Map[String, AnyRef]]
+ }))
+ filterBookmarkHierarchy(nextChildren, bookmarkId)
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ }
+
+ def getUnpublishedBookmarkHierarchy(request: Request, identifier: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[util.Map[String, AnyRef]] = {
+ if (StringUtils.isNotEmpty(identifier)) {
+ val parentHierarchy = fetchHierarchy(request, identifier + imgSuffix)
+ parentHierarchy.map(hierarchy => {
+ if (!hierarchy.isEmpty && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) {
+ val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String])
+ if (!bookmarkHierarchy.isEmpty) {
+ bookmarkHierarchy
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ } else {
+ new util.HashMap[String, AnyRef]()
+ }
+ })
+ } else {
+ Future(new util.HashMap[String, AnyRef]())
+ }
+ }
+
+ def validateShallowCopied(rootNodeMap: util.Map[String, AnyRef], operation: String, identifier: String) = {
+ val originData = rootNodeMap.getOrDefault("originData", new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]]
+ if (StringUtils.equalsIgnoreCase(originData.getOrElse("copyType", "").asInstanceOf[String], HierarchyConstants.COPY_TYPE_SHALLOW)) {
+ operation match {
+ case "add"=> throw new ClientException(HierarchyErrorCodes.ERR_ADD_HIERARCHY_DENIED, "Add Hierarchy is not allowed for partially (shallow) copied content : " + identifier)
+ case "remove"=> throw new ClientException(HierarchyErrorCodes.ERR_REMOVE_HIERARCHY_DENIED, "Remove Hierarchy is not allowed for partially (shallow) copied content : " + identifier)
+ }
+
+ }
+ }
+
+ def updateLatestLeafNodes(children: util.List[util.Map[String, AnyRef]], leafNodeMap: util.Map[String, AnyRef]): List[Any] = {
+ children.toList.map(content => {
+ if(StringUtils.equalsIgnoreCase("Default", content.getOrDefault("visibility", "").asInstanceOf[String])) {
+ val metadata: util.Map[String, AnyRef] = leafNodeMap.getOrDefault(content.get("identifier").asInstanceOf[String], new java.util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]]
+ if(HierarchyConstants.RETIRED_STATUS.equalsIgnoreCase(metadata.getOrDefault("status", HierarchyConstants.RETIRED_STATUS).asInstanceOf[String])){
+ children.remove(content)
+ } else {
+ if (objectTypeAsContentEnabled)
+ HierarchyBackwardCompatibilityUtil.setObjectTypeForRead(metadata, metadata.get("objectType").asInstanceOf[String])
+ content.putAll(metadata)
+ }
+ } else {
+ updateLatestLeafNodes(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], leafNodeMap)
+ }
+ })
+ }
+
+ def fetchAllLeafNodes(children: util.List[util.Map[String, AnyRef]], leafNodeIds: util.List[String]): List[Any] = {
+ children.toList.map(content => {
+ if(StringUtils.equalsIgnoreCase("Default", content.getOrDefault("visibility", "").asInstanceOf[String])) {
+ leafNodeIds.add(content.get("identifier").asInstanceOf[String])
+ leafNodeIds
+ } else {
+ fetchAllLeafNodes(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], leafNodeIds)
+ }
+ })
+ }
+
+ def getLatestLeafNodes(leafNodeIds : util.List[String])(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ if(CollectionUtils.isNotEmpty(leafNodeIds)) {
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.GRAPH_ID, HierarchyConstants.TAXONOMY_ID)
+ }
+ })
+ request.put("identifiers", leafNodeIds)
+ DataNode.list(request).map(nodes => {
+ val leafNodeMap: Map[String, AnyRef] = nodes.toList.map(node => (node.getIdentifier, NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true).asInstanceOf[AnyRef])).toMap
+ val imageNodeIds: util.List[String] = JavaConverters.seqAsJavaListConverter(leafNodeIds.toList.map(id => id + HierarchyConstants.IMAGE_SUFFIX)).asJava
+ request.put("identifiers", imageNodeIds)
+ DataNode.list(request).map(imageNodes => {
+ //val imageLeafNodeMap: Map[String, AnyRef] = imageNodes.toList.map(imageNode => (imageNode.getIdentifier.replaceAll(HierarchyConstants.IMAGE_SUFFIX, ""), NodeUtil.serialize(imageNode, null, HierarchyConstants.CONTENT_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION, true).asInstanceOf[AnyRef])).toMap
+ val imageLeafNodeMap: Map[String, AnyRef] = imageNodes.toList.map(imageNode => {
+ val identifier = imageNode.getIdentifier.replaceAll(HierarchyConstants.IMAGE_SUFFIX, "")
+ val metadata = NodeUtil.serialize(imageNode, null, imageNode.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true)
+ metadata.replace("identifier", identifier)
+ (identifier, metadata.asInstanceOf[AnyRef])
+ }).toMap
+ val updatedMap = leafNodeMap ++ imageLeafNodeMap
+ JavaConverters.mapAsJavaMapConverter(updatedMap).asJava
+ })
+ }).flatMap(f => f)
+ } else {
+ Future{new util.HashMap[String, AnyRef]()}
+ }
+
+ }
+
+ def updateContentMappingInChildren(children: util.List[util.Map[String, AnyRef]]): List[Any] = {
+ children.toList.map(content => {
+ if (mapPrimaryCategoriesEnabled)
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(content, content.get("objectType").asInstanceOf[String])
+ if (objectTypeAsContentEnabled)
+ HierarchyBackwardCompatibilityUtil.setObjectTypeForRead(content, content.get("objectType").asInstanceOf[String])
+ updateContentMappingInChildren(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]])
+ })
+ }
+
+ private def mapPrimaryCategories(hierarchy: java.util.Map[String, AnyRef]):util.Map[String, AnyRef] = {
+ val updatedHierarchy = new util.HashMap[String, AnyRef](hierarchy)
+ if (mapPrimaryCategoriesEnabled)
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(updatedHierarchy)
+ if (objectTypeAsContentEnabled)
+ HierarchyBackwardCompatibilityUtil.setObjectTypeForRead(updatedHierarchy, updatedHierarchy.get("objectType").asInstanceOf[String])
+ val children = new util.HashMap[String, AnyRef](hierarchy).getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]]
+ updateContentMappingInChildren(children)
+ updatedHierarchy
+ }
+
+ def validateLeafNodes(parentNode: java.util.Map[String, AnyRef], childNode: java.util.Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext) = {
+ val primaryCategory = parentNode.getOrDefault("primaryCategory", "").asInstanceOf[String]
+ val channel = parentNode.getOrDefault("channel", "_all")
+ val categoryId = if (StringUtils.isBlank(primaryCategory)) "" else "obj-cat:" + Slug.makeSlug(primaryCategory + "_" + parentNode.getOrDefault("objectType", "").asInstanceOf[String].toLowerCase() + "_" + channel)
+ val outRelations = DefinitionNode.getOutRelations(HierarchyConstants.GRAPH_ID, "1.0", parentNode.getOrDefault("objectType", "").asInstanceOf[String].toLowerCase().replace("image", ""), categoryId)
+ val configObjTypes: List[String] = outRelations.find(_.keySet.contains("children")).orNull.getOrElse("children", Map()).asInstanceOf[java.util.Map[String, AnyRef]].getOrElse("objects", new util.ArrayList[String]()).asInstanceOf[java.util.List[String]].toList
+ if(configObjTypes.nonEmpty && !configObjTypes.contains(childNode.getOrDefault("objectType", "").asInstanceOf[String]))
+ throw new ClientException("ERR_INVALID_CHILDREN", "Invalid Children objectType "+childNode.get("objectType")+" found for : "+childNode.get("identifier") + "| Please provide children having one of the objectType from "+ configObjTypes.asJava)
+ }
+}
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala
new file mode 100644
index 000000000..687c53551
--- /dev/null
+++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala
@@ -0,0 +1,507 @@
+package org.sunbird.managers
+
+import java.util.concurrent.CompletionException
+
+import org.apache.commons.collections4.{CollectionUtils, MapUtils}
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.dto.{Request, Response, ResponseHandler}
+import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ServerException}
+import org.sunbird.common.{DateUtils, JsonUtils, Platform}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.graph.common.Identifier
+import org.sunbird.graph.dac.model.Node
+import org.sunbird.graph.external.ExternalPropsManager
+import org.sunbird.graph.nodes.DataNode
+import org.sunbird.graph.schema.DefinitionNode
+import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
+import org.sunbird.telemetry.logger.TelemetryManager
+import org.sunbird.utils.{HierarchyBackwardCompatibilityUtil, HierarchyConstants, HierarchyErrorCodes}
+
+import scala.collection.JavaConversions._
+import scala.collection.mutable
+import scala.concurrent.{ExecutionContext, Future}
+
+object UpdateHierarchyManager {
+
+ @throws[Exception]
+ def updateHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = {
+ validateRequest(request)
+ val nodesModified: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.NODES_MODIFIED).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ val hierarchy: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.HIERARCHY).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ val rootId: String = getRootId(nodesModified, hierarchy)
+ request.getContext.put(HierarchyConstants.ROOT_ID, rootId)
+ getValidatedRootNode(rootId, request).map(node => {
+ getExistingHierarchy(request, node).map(existingHierarchy => {
+ val existingChildren = existingHierarchy.getOrElse(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.HashMap[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ val nodes = List(node)
+ addChildNodesInNodeList(existingChildren, request, nodes).map(list => (existingHierarchy, list))
+ }).flatMap(f => f)
+ .map(result => {
+ val nodes = result._2
+ TelemetryManager.info("NodeList final size: " + nodes.size)
+ val duplicates = nodes.groupBy(node => node.getIdentifier).map(t => t._1 -> t._2.size).toMap
+ //TelemetryManager.info("NodeList for root with duplicates :" + rootId +" :: " + ScalaJsonUtils.serialize(duplicates))
+ val nodeMap: Map[String, AnyRef] = nodes.map(node => node.getIdentifier -> node.getMetadata.get("visibility")).toMap
+ //TelemetryManager.info("NodeList for root id :" + rootId +" :: " + ScalaJsonUtils.serialize(nodeMap))
+ val idMap: mutable.Map[String, String] = mutable.Map()
+ idMap += (rootId -> rootId)
+ updateNodesModifiedInNodeList(nodes, nodesModified, request, idMap).map(modifiedNodeList => {
+
+ getChildrenHierarchy(modifiedNodeList, rootId, hierarchy, idMap, result._1).map(children => {
+ TelemetryManager.log("Children for root id :" + rootId +" :: " + JsonUtils.serialize(children))
+ updateHierarchyData(rootId, children, modifiedNodeList, request).map(node => {
+ val response = ResponseHandler.OK()
+ response.put(HierarchyConstants.CONTENT_ID, rootId)
+ idMap.remove(rootId)
+ response.put(HierarchyConstants.IDENTIFIERS, mapAsJavaMap(idMap))
+ if (request.getContext.getOrDefault("shouldImageDelete", false.asInstanceOf[AnyRef]).asInstanceOf[Boolean])
+ deleteHierarchy(request)
+ Future(response)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ })
+ }).flatMap(f => f).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+
+ private def validateRequest(request: Request)(implicit ec: ExecutionContext): Unit = {
+ if (!request.getRequest.contains(HierarchyConstants.NODES_MODIFIED) && !request.getRequest.contains(HierarchyConstants.HIERARCHY))
+ throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Hierarchy data is empty")
+ }
+
+ /**
+ * Checks if root id is empty, all black or image id
+ *
+ * @param nodesModified
+ * @param hierarchy
+ * @param ec
+ * @return
+ */
+ private def getRootId(nodesModified: java.util.HashMap[String, AnyRef], hierarchy: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext): String = {
+ val rootId: String = nodesModified.keySet()
+ .find(key => nodesModified.get(key).asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean])
+ .getOrElse(hierarchy.keySet().find(key => hierarchy.get(key).asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean]).orNull)
+ if (StringUtils.isEmpty(rootId) && StringUtils.isAllBlank(rootId) || StringUtils.contains(rootId, HierarchyConstants.IMAGE_SUFFIX))
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "Please Provide Valid Root Node Identifier")
+ rootId
+ }
+
+ //Check if you can combine the below methods
+ private def getValidatedRootNode(identifier: String, request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val req = new Request(request)
+ req.put(HierarchyConstants.IDENTIFIER, identifier)
+ req.put(HierarchyConstants.MODE, HierarchyConstants.EDIT_MODE)
+ DataNode.read(req).map(rootNode => {
+ val metadata: java.util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new java.util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])
+ if (!StringUtils.equals(metadata.get(HierarchyConstants.MIME_TYPE).asInstanceOf[String], HierarchyConstants.COLLECTION_MIME_TYPE)) {
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "Invalid MimeType for Root Node Identifier : " + identifier)
+ TelemetryManager.error("UpdateHierarchyManager.getValidatedRootNode :: Invalid MimeType for Root node id: " + identifier)
+ }
+ //Todo: Remove if not required
+ if (null == metadata.get(HierarchyConstants.VERSION) || metadata.get(HierarchyConstants.VERSION).asInstanceOf[Number].intValue < 2) {
+ TelemetryManager.error("UpdateHierarchyManager.getValidatedRootNode :: Invalid Content Version for Root node id: " + identifier)
+ throw new ClientException(HierarchyErrorCodes.ERR_INVALID_ROOT_ID, "The collection version is not up to date " + identifier)
+ }
+ val originData = metadata.getOrDefault("originData", new java.util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ if (StringUtils.equalsIgnoreCase(originData.getOrElse("copyType", "").asInstanceOf[String], HierarchyConstants.COPY_TYPE_SHALLOW))
+ throw new ClientException(HierarchyErrorCodes.ERR_HIERARCHY_UPDATE_DENIED, "Hierarchy update is not allowed for partially (shallow) copied content : " + identifier)
+ rootNode.getMetadata.put(HierarchyConstants.VERSION, HierarchyConstants.LATEST_CONTENT_VERSION)
+ //TODO: Remove the Populate category mapping before updating for backward
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(rootNode.getMetadata)
+ HierarchyBackwardCompatibilityUtil.setNewObjectType(rootNode)
+ rootNode
+ })
+ }
+
+ private def getExistingHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[java.util.HashMap[String, AnyRef]] = {
+ fetchHierarchy(request, rootNode).map(hierarchyString => {
+ if (!hierarchyString.asInstanceOf[String].isEmpty) {
+ JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.HashMap[String, AnyRef]])
+ } else new java.util.HashMap[String, AnyRef]()
+ })
+ }
+
+ private def fetchHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Any] = {
+ val req = new Request(request)
+ req.put(HierarchyConstants.IDENTIFIER, rootNode.getIdentifier)
+ oec.graphService.readExternalProps(req, List(HierarchyConstants.HIERARCHY)).map(response => {
+ if (ResponseHandler.checkError(response) && ResponseHandler.isResponseNotFoundError(response)) {
+ if (CollectionUtils.containsAny(HierarchyConstants.HIERARCHY_LIVE_STATUS, rootNode.getMetadata.get("status").asInstanceOf[String]))
+ throw new ServerException(HierarchyErrorCodes.ERR_HIERARCHY_NOT_FOUND, "No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier)
+ else {
+ if (rootNode.getMetadata.containsKey("pkgVersion"))
+ req.put(HierarchyConstants.IDENTIFIER, rootNode.getIdentifier.replace(HierarchyConstants.IMAGE_SUFFIX, ""))
+ else {
+ //TODO: Remove should Image be deleted after migration
+ request.getContext.put("shouldImageDelete", shouldImageBeDeleted(rootNode).asInstanceOf[AnyRef])
+ req.put(HierarchyConstants.IDENTIFIER, if (!rootNode.getIdentifier.endsWith(HierarchyConstants.IMAGE_SUFFIX)) rootNode.getIdentifier + HierarchyConstants.IMAGE_SUFFIX else rootNode.getIdentifier)
+ }
+ oec.graphService.readExternalProps(req, List(HierarchyConstants.HIERARCHY)).map(resp => {
+ resp.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String]
+ }) recover { case e: ResourceNotFoundException => TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier) }
+ }
+ } else Future(response.getResult.toMap.getOrElse(HierarchyConstants.HIERARCHY, "").asInstanceOf[String])
+ }).flatMap(f => f)
+ }
+
+ private def addChildNodesInNodeList(childrenMaps: java.util.List[java.util.Map[String, AnyRef]], request: Request, nodes: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[scala.collection.immutable.List[Node]] = {
+ if (CollectionUtils.isNotEmpty(childrenMaps)) {
+ val futures = childrenMaps.map(child => {
+// println("Executing for child : " + child.get("identifier"));
+ addNodeToList(child, request, nodes).map(modifiedList => {
+ if (!StringUtils.equalsIgnoreCase(HierarchyConstants.DEFAULT, child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+// println("Calling next level for child : " + child.get("identifier"));
+ addChildNodesInNodeList(child.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]], request, modifiedList)
+ } else
+ Future(modifiedList)
+ }).flatMap(f => f)
+ }).toList
+ Future.sequence(futures).map(f => f.flatten.distinct)
+ } else {
+ Future(nodes)
+ }
+ }
+
+ private def addNodeToList(child: java.util.Map[String, AnyRef], request: Request, nodes: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[scala.collection.immutable.List[Node]] = {
+ if (StringUtils.isNotEmpty(child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String]))
+ if (StringUtils.equalsIgnoreCase(HierarchyConstants.DEFAULT, child.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+ getContentNode(child.getOrDefault(HierarchyConstants.IDENTIFIER, "").asInstanceOf[String], HierarchyConstants.TAXONOMY_ID).map(node => {
+ node.getMetadata.put(HierarchyConstants.DEPTH, child.get(HierarchyConstants.DEPTH))
+ node.getMetadata.put(HierarchyConstants.PARENT, child.get(HierarchyConstants.PARENT))
+ node.getMetadata.put(HierarchyConstants.INDEX, child.get(HierarchyConstants.INDEX))
+ //TODO: Remove the Populate category mapping before updating for backward
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(node.getMetadata, node.getObjectType)
+ HierarchyBackwardCompatibilityUtil.setNewObjectType(node)
+ val updatedNodes = node :: nodes
+ updatedNodes
+ }) recoverWith { case e: CompletionException => throw e.getCause }
+ } else {
+ val childData: java.util.Map[String, AnyRef] = new java.util.HashMap[String, AnyRef]
+ childData.putAll(child)
+ childData.remove(HierarchyConstants.CHILDREN)
+ childData.put(HierarchyConstants.STATUS, "Draft")
+ //TODO: Remove the Populate category mapping before updating for backward
+ val rootNode = getTempNode(nodes, request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String])
+ childData.put(HierarchyConstants.CHANNEL, rootNode.getMetadata.get(HierarchyConstants.CHANNEL))
+ childData.put(HierarchyConstants.AUDIENCE, rootNode.getMetadata.get(HierarchyConstants.AUDIENCE) )
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(childData)
+ val node = NodeUtil.deserialize(childData, request.getContext.get(HierarchyConstants.SCHEMA_NAME).asInstanceOf[String], DefinitionNode.getRelationsMap(request))
+ HierarchyBackwardCompatibilityUtil.setNewObjectType(node)
+ val updatedNodes = node :: nodes
+ Future(updatedNodes)
+ }
+ else {
+ //println("Visibility is empty for child :" + child)
+ Future(nodes)
+ }
+ }
+
+
+ private def updateNodesModifiedInNodeList(nodeList: List[Node], nodesModified: java.util.HashMap[String, AnyRef], request: Request, idMap: mutable.Map[String, String])(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ updateRootNode(request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String], nodeList, nodesModified)
+ val futures = nodesModified.filter(nodeModified => !StringUtils.startsWith(request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String], nodeModified._1))
+ .map(nodeModified => {
+ val metadata = nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ metadata.remove(HierarchyConstants.DIALCODES)
+ metadata.put(HierarchyConstants.STATUS, "Draft")
+ metadata.put(HierarchyConstants.LAST_UPDATED_ON, DateUtils.formatCurrentDate)
+ if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].containsKey(HierarchyConstants.IS_NEW)
+ && nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.IS_NEW).asInstanceOf[Boolean]) {
+ if (!nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean])
+ metadata.put(HierarchyConstants.VISIBILITY, HierarchyConstants.PARENT)
+ if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].contains(HierarchyConstants.SET_DEFAULT_VALUE))
+ createNewNode(nodeModified._1, idMap, metadata, nodeList, request, nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.SET_DEFAULT_VALUE).asInstanceOf[Boolean])
+ else
+ createNewNode(nodeModified._1, idMap, metadata, nodeList, request)
+ } else {
+ updateTempNode(nodeModified._1, nodeList, idMap, metadata)
+ Future(nodeList.distinct)
+ }
+ })
+ if (CollectionUtils.isNotEmpty(futures))
+ Future.sequence(futures.toList).map(f => f.flatten)
+ else Future(nodeList)
+ }
+
+ private def updateRootNode(rootId: String, nodeList: List[Node], nodesModified: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext): Unit = {
+ if (nodesModified.containsKey(rootId)) {
+ val metadata = nodesModified.getOrDefault(rootId, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]]
+ updateNodeList(nodeList, rootId, metadata)
+ nodesModified.remove(rootId)
+ }
+ }
+
+ private def createNewNode(nodeId: String, idMap: mutable.Map[String, String], metadata: java.util.HashMap[String, AnyRef], nodeList: List[Node], request: Request, setDefaultValue: Boolean = true)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val identifier: String = Identifier.getIdentifier(HierarchyConstants.TAXONOMY_ID, Identifier.getUniqueIdFromTimestamp)
+ idMap += (nodeId -> identifier)
+ metadata.put(HierarchyConstants.IDENTIFIER, identifier)
+ metadata.put(HierarchyConstants.CODE, nodeId)
+ metadata.put(HierarchyConstants.VERSION_KEY, System.currentTimeMillis + "")
+ metadata.put(HierarchyConstants.CREATED_ON, DateUtils.formatCurrentDate)
+ metadata.put(HierarchyConstants.LAST_STATUS_CHANGED_ON, DateUtils.formatCurrentDate)
+ val rootNode = getTempNode(nodeList, request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String])
+ metadata.put(HierarchyConstants.CHANNEL, rootNode.getMetadata.get(HierarchyConstants.CHANNEL))
+ metadata.put(HierarchyConstants.AUDIENCE, rootNode.getMetadata.get(HierarchyConstants.AUDIENCE) )
+ val createRequest: Request = new Request(request)
+ //TODO: Remove the Populate category mapping before updating for backward
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(metadata)
+ createRequest.setRequest(metadata)
+ DefinitionNode.validate(createRequest, setDefaultValue).map(node => {
+ node.setGraphId(HierarchyConstants.TAXONOMY_ID)
+ node.setNodeType(HierarchyConstants.DATA_NODE)
+ //Object type mapping
+ HierarchyBackwardCompatibilityUtil.setNewObjectType(node)
+ val updatedList = node :: nodeList
+ updatedList.distinct
+ })
+ }
+
+ private def updateTempNode(nodeId: String, nodeList: List[Node], idMap: mutable.Map[String, String], metadata: java.util.HashMap[String, AnyRef])(implicit ec: ExecutionContext): Unit = {
+ val tempNode: Node = getTempNode(nodeList, nodeId)
+ if (null != tempNode && StringUtils.isNotBlank(tempNode.getIdentifier)) {
+ metadata.put(HierarchyConstants.IDENTIFIER, tempNode.getIdentifier)
+ idMap += (nodeId -> tempNode.getIdentifier)
+ updateNodeList(nodeList, tempNode.getIdentifier, metadata)
+ } else throw new ResourceNotFoundException(HierarchyErrorCodes.ERR_CONTENT_NOT_FOUND, "Content not found with identifier: " + nodeId)
+ }
+
+ private def validateNodes(nodeList: java.util.List[Node], rootId: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = {
+ val nodesToValidate = nodeList.filter(node => StringUtils.equals(HierarchyConstants.PARENT, node.getMetadata.get(HierarchyConstants.VISIBILITY).asInstanceOf[String]) || StringUtils.equalsAnyIgnoreCase(rootId, node.getIdentifier)).toList
+ DefinitionNode.updateJsonPropsInNodes(nodeList.toList, HierarchyConstants.TAXONOMY_ID, HierarchyConstants.COLLECTION_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION)
+ //TODO: Use actual object schema instead of collection, when another object with visibility parent introduced.
+ DefinitionNode.validateContentNodes(nodesToValidate, HierarchyConstants.TAXONOMY_ID, HierarchyConstants.COLLECTION_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION)
+ }
+
+ def constructHierarchy(list: List[java.util.Map[String, AnyRef]]): java.util.Map[String, AnyRef] = {
+ val hierarchy: java.util.Map[String, AnyRef] = list.filter(root => root.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == 0).head
+ if (MapUtils.isNotEmpty(hierarchy)) {
+ val maxDepth = list.map(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue()).max
+ for (i <- 0 to maxDepth) {
+ val depth = i
+ val currentLevelNodes: Map[String, List[java.util.Map[String, Object]]] = list.filter(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == depth).groupBy(_.get("identifier").asInstanceOf[String].replaceAll(".img", ""))
+ val nextLevel: List[java.util.Map[String, AnyRef]] = list.filter(node => node.get(HierarchyConstants.DEPTH).asInstanceOf[Number].intValue() == (depth + 1))
+ if (CollectionUtils.isNotEmpty(nextLevel) && MapUtils.isNotEmpty(currentLevelNodes)) {
+ nextLevel.foreach(e => {
+ val parentId = e.get("parent").asInstanceOf[String]
+ currentLevelNodes.getOrDefault(parentId, List[java.util.Map[String, AnyRef]]()).foreach(parent => {
+ val children = parent.getOrDefault(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.Map[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ children.add(e)
+ parent.put(HierarchyConstants.CHILDREN, sortByIndex(children))
+ })
+ })
+ }
+ }
+ }
+ hierarchy
+ }
+
+ @throws[Exception]
+ private def getChildrenHierarchy(nodeList: List[Node], rootId: String, hierarchyData: java.util.HashMap[String, AnyRef], idMap: mutable.Map[String, String], existingHierarchy: java.util.Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[java.util.List[java.util.Map[String, AnyRef]]] = {
+ val childrenIdentifiersMap: Map[String, Map[String, Int]] = getChildrenIdentifiersMap(hierarchyData, idMap, existingHierarchy)
+// TelemetryManager.log("Children Id map for root id :" + rootId + " :: " + ScalaJsonUtils.serialize(childrenIdentifiersMap))
+ getPreparedHierarchyData(nodeList, rootId, childrenIdentifiersMap).map(nodeMaps => {
+ TelemetryManager.info("prepared hierarchy list without filtering: " + nodeMaps.size())
+ val filteredNodeMaps = nodeMaps.filter(nodeMap => null != nodeMap.get(HierarchyConstants.DEPTH)).toList
+ TelemetryManager.info("prepared hierarchy list with filtering: " + filteredNodeMaps.size())
+// TelemetryManager.log("filteredNodeMaps for root id :" + rootId + " :: " + ScalaJsonUtils.serialize(filteredNodeMaps))
+ val hierarchyMap = constructHierarchy(filteredNodeMaps)
+ if (MapUtils.isNotEmpty(hierarchyMap)) {
+ hierarchyMap.getOrDefault(HierarchyConstants.CHILDREN, new java.util.ArrayList[java.util.Map[String, AnyRef]]()).asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ .filter(child => MapUtils.isNotEmpty(child))
+ }
+ else
+ new java.util.ArrayList[java.util.Map[String, AnyRef]]()
+
+ })
+ }
+
+ private def getChildrenIdentifiersMap(hierarchyData: java.util.Map[String, AnyRef], idMap: mutable.Map[String, String], existingHierarchy: java.util.Map[String, AnyRef]): Map[String, Map[String, Int]] = {
+ if (MapUtils.isNotEmpty(hierarchyData)) {
+ hierarchyData.map(entry => idMap.getOrDefault(entry._1, entry._1) -> entry._2.asInstanceOf[java.util.HashMap[String, AnyRef]]
+ .get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[String]]
+ .map(id => idMap.getOrDefault(id, id)).zipWithIndex.toMap).toMap
+ } else {
+ val tempChildMap: java.util.Map[String, Map[String, Int]] = new java.util.HashMap[String, Map[String, Int]]()
+ val tempResourceMap: java.util.Map[String, Map[String, Int]] = new java.util.HashMap[String, Map[String, Int]]()
+ getChildrenIdMapFromExistingHierarchy(existingHierarchy, tempChildMap, tempResourceMap)
+ tempChildMap.putAll(tempResourceMap)
+ tempChildMap.toMap
+ }
+ }
+
+ private def getChildrenIdMapFromExistingHierarchy(existingHierarchy: java.util.Map[String, AnyRef], tempChildMap: java.util.Map[String, Map[String, Int]], tempResourceMap: java.util.Map[String, Map[String, Int]]): Unit = {
+ if (existingHierarchy.containsKey(HierarchyConstants.CHILDREN) && CollectionUtils.isNotEmpty(existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]])) {
+ tempChildMap.put(existingHierarchy.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String], existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]]
+ .map(child => child.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String] -> child.get(HierarchyConstants.INDEX).asInstanceOf[Int]).toMap)
+ existingHierarchy.get(HierarchyConstants.CHILDREN).asInstanceOf[java.util.ArrayList[java.util.HashMap[String, AnyRef]]]
+ .foreach(child => getChildrenIdMapFromExistingHierarchy(child, tempChildMap, tempResourceMap))
+ } else
+ tempResourceMap.put(existingHierarchy.get(HierarchyConstants.IDENTIFIER).asInstanceOf[String], Map[String, Int]())
+ }
+
+ @throws[Exception]
+ private def getPreparedHierarchyData(nodeList: List[Node], rootId: String, childrenIdentifiersMap: Map[String, Map[String, Int]])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[java.util.List[java.util.Map[String, AnyRef]]] = {
+ if (MapUtils.isNotEmpty(childrenIdentifiersMap)) {
+ val updatedNodeList = getTempNode(nodeList, rootId) :: List()
+ updateHierarchyRelatedData(childrenIdentifiersMap.getOrElse(rootId, Map[String, Int]()), 1,
+ rootId, nodeList, childrenIdentifiersMap, updatedNodeList).map(finalEnrichedNodeList => {
+ TelemetryManager.info("Final enriched list size: " + finalEnrichedNodeList.size)
+ val childNodeIds = finalEnrichedNodeList.map(node => node.getIdentifier).filterNot(id => rootId.equalsIgnoreCase(id)).distinct
+ TelemetryManager.info("Final enriched ids (childNodes): " + childNodeIds + " :: size: " + childNodeIds.size)
+ // UNDERSTANDING: below we used nodeList to update DEPTH and CHILD_NODES. It automatically updated to finalEnrichedNodeList.
+ // Because, the Node object is a Java POJO with metadata using java.util.Map.
+ updateNodeList(nodeList, rootId, new java.util.HashMap[String, AnyRef]() {
+ put(HierarchyConstants.DEPTH, 0.asInstanceOf[AnyRef])
+ put(HierarchyConstants.CHILD_NODES, new java.util.ArrayList[String](childNodeIds))
+ })
+ validateNodes(finalEnrichedNodeList, rootId).map(result => HierarchyManager.convertNodeToMap(finalEnrichedNodeList))
+ }).flatMap(f => f)
+ } else {
+ updateNodeList(nodeList, rootId, new java.util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.DEPTH, 0.asInstanceOf[AnyRef])
+ }
+ })
+ validateNodes(nodeList, rootId).map(result => HierarchyManager.convertNodeToMap(nodeList))
+ }
+ }
+
+ @throws[Exception]
+ private def updateHierarchyRelatedData(childrenIds: Map[String, Int], depth: Int, parent: String, nodeList: List[Node], hierarchyStructure: Map[String, Map[String, Int]], enrichedNodeList: scala.collection.immutable.List[Node])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[List[Node]] = {
+ val futures = childrenIds.map(child => {
+ val id = child._1
+ val index = child._2 + 1
+ val tempNode = getTempNode(nodeList, id)
+ if (null != tempNode && StringUtils.equalsIgnoreCase(HierarchyConstants.PARENT, tempNode.getMetadata.get(HierarchyConstants.VISIBILITY).asInstanceOf[String])) {
+ populateHierarchyRelatedData(tempNode, depth, index, parent)
+ val nxtEnrichedNodeList = tempNode :: enrichedNodeList
+ if (MapUtils.isNotEmpty(hierarchyStructure.getOrDefault(child._1, Map[String, Int]())))
+ updateHierarchyRelatedData(hierarchyStructure.getOrDefault(child._1, Map[String, Int]()),
+ tempNode.getMetadata.get(HierarchyConstants.DEPTH).asInstanceOf[Int] + 1, id, nodeList, hierarchyStructure, nxtEnrichedNodeList)
+ else
+ Future(nxtEnrichedNodeList)
+ } else {
+// TelemetryManager.info("Get ContentNode as TempNode is null for ID: " + id)
+ getContentNode(id, HierarchyConstants.TAXONOMY_ID).map(node => {
+ val parentNode: Node = nodeList.find(p => p.getIdentifier.equals(parent)).orNull
+ val parentMetadata: java.util.Map[String, AnyRef] = NodeUtil.serialize(parentNode, new java.util.ArrayList[String](), parentNode.getObjectType.toLowerCase, "1.0")
+ val childMetadata: java.util.Map[String, AnyRef] = NodeUtil.serialize(node, new java.util.ArrayList[String](), node.getObjectType.toLowerCase, "1.0")
+ HierarchyManager.validateLeafNodes(parentMetadata, childMetadata)
+ populateHierarchyRelatedData(node, depth, index, parent)
+ node.getMetadata.put(HierarchyConstants.VISIBILITY, HierarchyConstants.DEFAULT)
+ //TODO: Populate category mapping before updating for backward
+ HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(node.getMetadata, node.getObjectType)
+ HierarchyBackwardCompatibilityUtil.setNewObjectType(node)
+ val nxtEnrichedNodeList = node :: enrichedNodeList
+ if (MapUtils.isNotEmpty(hierarchyStructure.getOrDefault(id, Map[String, Int]()))) {
+ updateHierarchyRelatedData(hierarchyStructure.getOrDefault(id, Map[String, Int]()), node.getMetadata.get(HierarchyConstants.DEPTH).asInstanceOf[Int] + 1, id, nodeList, hierarchyStructure, nxtEnrichedNodeList)
+ } else
+ Future(nxtEnrichedNodeList)
+ }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause }
+ }
+ })
+ if (CollectionUtils.isNotEmpty(futures)) {
+ val listOfFutures = Future.sequence(futures.toList)
+ listOfFutures.map(f => f.flatten.distinct)
+ } else
+ Future(enrichedNodeList)
+ }
+
+ private def populateHierarchyRelatedData(tempNode: Node, depth: Int, index: Int, parent: String) = {
+ tempNode.getMetadata.put(HierarchyConstants.DEPTH, depth.asInstanceOf[AnyRef])
+ tempNode.getMetadata.put(HierarchyConstants.PARENT, parent.replaceAll(".img", ""))
+ tempNode.getMetadata.put(HierarchyConstants.INDEX, index.asInstanceOf[AnyRef])
+ }
+
+ /**
+ * This method is to check if all the children of the parent entity are present in the populated map
+ *
+ * @param children
+ * @param populatedChildMap
+ * @return
+ */
+ def isFullyPopulated(children: List[String], populatedChildMap: mutable.Map[_, _]): Boolean = {
+ children.forall(child => populatedChildMap.containsKey(child))
+ }
+
+ def updateHierarchyData(rootId: String, children: java.util.List[java.util.Map[String, AnyRef]], nodeList: List[Node], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val node = getTempNode(nodeList, rootId)
+ val updatedHierarchy = new java.util.HashMap[String, AnyRef]()
+ updatedHierarchy.put(HierarchyConstants.IDENTIFIER, rootId)
+ updatedHierarchy.put(HierarchyConstants.CHILDREN, children)
+ val req = new Request(request)
+ req.getContext.put(HierarchyConstants.IDENTIFIER, rootId)
+ val metadata = cleanUpRootData(node)
+ req.getRequest.putAll(metadata)
+ req.put(HierarchyConstants.HIERARCHY, ScalaJsonUtils.serialize(updatedHierarchy))
+ req.put(HierarchyConstants.IDENTIFIER, rootId)
+ req.put(HierarchyConstants.CHILDREN, new java.util.ArrayList())
+ req.put(HierarchyConstants.CONCEPTS, new java.util.ArrayList())
+ DataNode.update(req)
+ }
+
+ private def cleanUpRootData(node: Node)(implicit oec: OntologyEngineContext, ec: ExecutionContext): java.util.Map[String, AnyRef] = {
+ DefinitionNode.getRestrictedProperties(HierarchyConstants.TAXONOMY_ID, HierarchyConstants.SCHEMA_VERSION, HierarchyConstants.OPERATION_UPDATE_HIERARCHY, HierarchyConstants.COLLECTION_SCHEMA_NAME)
+ .foreach(key => node.getMetadata.remove(key))
+ node.getMetadata.remove(HierarchyConstants.STATUS)
+ node.getMetadata.remove(HierarchyConstants.LAST_UPDATED_ON)
+ node.getMetadata.remove(HierarchyConstants.LAST_STATUS_CHANGED_ON)
+ node.getMetadata
+ }
+
+ /**
+ * Get the Node with ID provided from List else return Null.
+ *
+ * @param nodeList
+ * @param id
+ * @return
+ */
+ private def getTempNode(nodeList: List[Node], id: String) = {
+ nodeList.find(node => StringUtils.startsWith(node.getIdentifier, id)).orNull
+ }
+
+ private def updateNodeList(nodeList: List[Node], id: String, metadata: java.util.HashMap[String, AnyRef]): Unit = {
+ nodeList.foreach(node => {
+ if(node.getIdentifier.startsWith(id)){
+ node.getMetadata.putAll(metadata)
+ }
+ })
+ }
+
+ def getContentNode(identifier: String, graphId: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = {
+ val request: Request = new Request()
+ request.setContext(new java.util.HashMap[String, AnyRef]() {
+ {
+ put(HierarchyConstants.GRAPH_ID, graphId)
+ put(HierarchyConstants.VERSION, HierarchyConstants.SCHEMA_VERSION)
+ put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.CONTENT_OBJECT_TYPE)
+ put(HierarchyConstants.SCHEMA_NAME, HierarchyConstants.CONTENT_SCHEMA_NAME)
+ }
+ })
+ request.setObjectType(HierarchyConstants.CONTENT_OBJECT_TYPE)
+ request.put(HierarchyConstants.IDENTIFIER, identifier)
+ request.put(HierarchyConstants.MODE, HierarchyConstants.READ_MODE)
+ request.put(HierarchyConstants.FIELDS, new java.util.ArrayList[String]())
+ DataNode.read(request)
+ }
+
+ private def shouldImageBeDeleted(rootNode: Node): Boolean = {
+ val flag = if (Platform.config.hasPath("collection.image.migration.enabled")) Platform.config.getBoolean("collection.image.migration.enabled") else false
+ // flag && !CollectionUtils.containsAny(HierarchyConstants.HIERARCHY_LIVE_STATUS, rootNode.getMetadata.get(HierarchyConstants.STATUS).asInstanceOf[String]) &&
+ // !rootNode.getMetadata.containsKey("pkgVersion")
+ flag
+ }
+
+ def sortByIndex(childrenMaps: java.util.List[java.util.Map[String, AnyRef]]): java.util.List[java.util.Map[String, AnyRef]] = {
+ bufferAsJavaList(childrenMaps.sortBy(_.get("index").asInstanceOf[Int]))
+ }
+
+
+ def deleteHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Response] = {
+ val req = new Request(request)
+ val rootId = request.getContext.get(HierarchyConstants.ROOT_ID).asInstanceOf[String]
+ req.put(HierarchyConstants.IDENTIFIERS, if (rootId.contains(HierarchyConstants.IMAGE_SUFFIX)) List(rootId) else List(rootId + HierarchyConstants.IMAGE_SUFFIX))
+ oec.graphService.deleteExternalProps(req)
+ }
+
+}
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyBackwardCompatibilityUtil.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyBackwardCompatibilityUtil.scala
new file mode 100644
index 000000000..83aef6b53
--- /dev/null
+++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyBackwardCompatibilityUtil.scala
@@ -0,0 +1,70 @@
+package org.sunbird.utils
+
+import java.util
+
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.common.Platform
+import org.sunbird.graph.dac.model.Node
+
+import scala.collection.JavaConverters._
+
+object HierarchyBackwardCompatibilityUtil {
+
+ val categoryMap: java.util.Map[String, AnyRef] = Platform.getAnyRef("contentTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val categoryMapForMimeType: java.util.Map[String, AnyRef] = Platform.getAnyRef("mimeTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val categoryMapForResourceType: java.util.Map[String, AnyRef] = Platform.getAnyRef("resourceTypeToPrimaryCategory",
+ new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]
+ val mimeTypesToCheck = List("application/vnd.ekstep.h5p-archive", "application/vnd.ekstep.html-archive", "application/vnd.android.package-archive",
+ "video/webm", "video/x-youtube", "video/mp4")
+ val objectTypes = List("Content", "Collection")
+
+ def setContentAndCategoryTypes(input: util.Map[String, AnyRef], objType: String = ""): Unit = {
+ if(StringUtils.isBlank(objType) || objectTypes.contains(objType)) {
+ val contentType = input.get("contentType").asInstanceOf[String]
+ val primaryCategory = input.get("primaryCategory").asInstanceOf[String]
+ val (updatedContentType, updatedPrimaryCategory): (String, String) = (contentType, primaryCategory) match {
+ case (x: String, y: String) => (x, y)
+ case ("Resource", y) => (contentType, getCategoryForResource(input.getOrDefault("mimeType", "").asInstanceOf[String],
+ input.getOrDefault("resourceType", "").asInstanceOf[String]))
+ case (x: String, y) => (x, categoryMap.get(x).asInstanceOf[String])
+ case (x, y: String) => (categoryMap.asScala.filter(entry => StringUtils.equalsIgnoreCase(entry._2.asInstanceOf[String], y)).keys.headOption.getOrElse(""), y)
+ case _ => (contentType, primaryCategory)
+ }
+
+ input.put("contentType", updatedContentType)
+ input.put("primaryCategory", updatedPrimaryCategory)
+ }
+ }
+
+ private def getCategoryForResource(mimeType: String, resourceType: String): String = (mimeType, resourceType) match {
+ case ("", "") => "Learning Resource"
+ case (x: String, "") => categoryMapForMimeType.get(x).asInstanceOf[util.List[String]].asScala.headOption.getOrElse("Learning Resource")
+ case (x: String, y: String) => if (mimeTypesToCheck.contains(x)) categoryMapForMimeType.get(x).asInstanceOf[util.List[String]].asScala.headOption.getOrElse("Learning Resource") else categoryMapForResourceType.getOrDefault(y, "Learning Resource").asInstanceOf[String]
+ case _ => "Learning Resource"
+ }
+ def setObjectTypeForRead(result: java.util.Map[String, AnyRef], objectType: String = ""): Unit = {
+ if(objectTypes.contains(objectType))
+ result.put("objectType", "Content")
+ }
+
+ def setNewObjectType(node: Node) = {
+ val metadata = node.getMetadata
+ val mimeType = metadata.getOrDefault("mimeType", "").asInstanceOf[String]
+ val contentType = metadata.getOrDefault("contentType", "").asInstanceOf[String]
+ val objectType = metadata.getOrDefault("objectType", "").asInstanceOf[String]
+ val primaryCategory = metadata.getOrDefault("primaryCategory", "").asInstanceOf[String]
+
+ if (StringUtils.isNotBlank(mimeType) && StringUtils.equalsIgnoreCase(mimeType, HierarchyConstants.COLLECTION_MIME_TYPE)) {
+ metadata.put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ node.setObjectType(HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ } else if ((StringUtils.isNotBlank(contentType) && StringUtils.equalsIgnoreCase(contentType, HierarchyConstants.ASSET_CONTENT_TYPE))
+ || (StringUtils.isNotBlank(primaryCategory) && StringUtils.equalsIgnoreCase(primaryCategory, HierarchyConstants.ASSET_CONTENT_TYPE))) {
+ metadata.put(HierarchyConstants.OBJECT_TYPE, HierarchyConstants.ASSET_OBJECT_TYPE)
+ node.setObjectType(HierarchyConstants.ASSET_OBJECT_TYPE)
+ } else {
+ metadata.put(HierarchyConstants.OBJECT_TYPE, objectType)
+ }
+ }
+}
\ No newline at end of file
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala
new file mode 100644
index 000000000..83c5d1da7
--- /dev/null
+++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala
@@ -0,0 +1,63 @@
+package org.sunbird.utils
+
+object HierarchyConstants {
+ val DATA: String = "data"
+ val DATA_NODE: String = "DATA_NODE"
+ val NODES_MODIFIED: String = "nodesModified"
+ val HIERARCHY: String = "hierarchy"
+ val ROOT: String = "root"
+ val SET_DEFAULT_VALUE: String = "setDefaultValue"
+ val COLLECTION_MIME_TYPE: String = "application/vnd.ekstep.content-collection"
+ val COLLECTION_SCHEMA_NAME: String = "collection"
+ val EVENT_SET_SCHEMA_NAME: String = "eventset"
+ val LATEST_CONTENT_VERSION: Integer = 2
+ val VERSION: String = "version"
+ val IDENTIFIER: String = "identifier"
+ val DEPTH: String = "depth"
+ val PARENT: String = "Parent"
+ val INDEX: String = "index"
+ val CHILDREN: String = "children"
+ val VISIBILITY: String = "visibility"
+ val TAXONOMY_ID: String = "domain"
+ val METADATA: String = "metadata"
+ val IS_NEW: String = "isNew"
+ val DIALCODES: String = "dialcodes"
+ val CONTENT_OBJECT_TYPE: String = "Content"
+ val OBJECT_TYPE = "objectType"
+ val STATUS: String = "status"
+ val LAST_UPDATED_ON: String = "lastUpdatedOn"
+ val CODE: String = "code"
+ val VERSION_KEY: String = "versionKey"
+ val CREATED_ON: String = "createdOn"
+ val LAST_STATUS_CHANGED_ON: String = "lastStatusChangedOn"
+ val CHILD_NODES: String = "childNodes"
+ val CONTENT_SCHEMA_NAME: String = "content"
+ val SCHEMA_NAME: String = "schemaName"
+ val SCHEMA_VERSION: String = "1.0"
+ val CONTENT_ID: String = "content_id"
+ val IDENTIFIERS: String = "identifiers"
+ val DEFAULT: String = "Default"
+ val CHANNEL: String = "channel"
+ val ROOT_ID: String = "rootId"
+ val HIERARCHY_LIVE_STATUS: List[String] = List("Live", "Unlisted", "Flagged")
+ val IMAGE_SUFFIX: String = ".img"
+ val GRAPH_ID: String = "graph_id"
+ val MODE: String = "mode"
+ val EDIT_MODE: String = "edit"
+ val READ_MODE: String = "read"
+ val CONCEPTS: String = "concepts"
+ val FIELDS: String = "fields"
+ val MIME_TYPE: String = "mimeType"
+ val COPY_TYPE_SHALLOW: String = "shallow"
+ val RETIRED_STATUS: String = "Retired"
+ val AUDIENCE: String = "audience"
+ val ASSET_SCHEMA_NAME: String = "asset"
+ val CONTENT_VERSION: String = "1.0"
+ val COLLECTION_VERSION: String = "1.0"
+ val ASSET_VERSION: String = "1.0"
+ val ASSET_CONTENT_TYPE: String = "Asset"
+ val COLLECTION_OBJECT_TYPE: String = "Collection"
+ val ASSET_OBJECT_TYPE: String = "Asset"
+ val OPERATION_UPDATE_HIERARCHY: String = "updateHierarchy"
+
+}
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala
new file mode 100644
index 000000000..7b7e8b9ec
--- /dev/null
+++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyErrorCodes.scala
@@ -0,0 +1,11 @@
+package org.sunbird.utils
+
+object HierarchyErrorCodes {
+ val ERR_INVALID_ROOT_ID: String = "ERR_INVALID_ROOT_ID"
+ val ERR_CONTENT_NOT_FOUND: String = "ERR_CONTENT_NOT_FOUND"
+ val ERR_HIERARCHY_NOT_FOUND: String = "ERR_HIERARCHY_NOT_FOUND"
+ val ERR_HIERARCHY_UPDATE_DENIED: String = "ERR_HIERARCHY_UPDATE_DENIED"
+ val ERR_ADD_HIERARCHY_DENIED: String = "ERR_ADD_HIERARCHY_DENIED"
+ val ERR_REMOVE_HIERARCHY_DENIED: String = "ERR_REMOVE_HIERARCHY_DENIED"
+
+}
diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala
new file mode 100644
index 000000000..e69de29bb
diff --git a/content-api/hierarchy-manager/src/test/resources/application.conf b/content-api/hierarchy-manager/src/test/resources/application.conf
new file mode 100644
index 000000000..046ae1a25
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/resources/application.conf
@@ -0,0 +1,536 @@
+# This is the main configuration file for the application.
+# https://www.playframework.com/documentation/latest/ConfigFile
+# ~~~~~
+# Play uses HOCON as its configuration file format. HOCON has a number
+# of advantages over other config formats, but there are two things that
+# can be used when modifying settings.
+#
+# You can include other configuration files in this main application.conf file:
+#include "extra-config.conf"
+#
+# You can declare variables and substitute for them:
+#mykey = ${some.value}
+#
+# And if an environment variable exists when there is no other substitution, then
+# HOCON will fall back to substituting environment variable:
+#mykey = ${JAVA_HOME}
+
+## Akka
+# https://www.playframework.com/documentation/latest/ScalaAkka#Configuration
+# https://www.playframework.com/documentation/latest/JavaAkka#Configuration
+# ~~~~~
+# Play uses Akka internally and exposes Akka Streams and actors in Websockets and
+# other streaming HTTP responses.
+akka {
+ # "akka.log-config-on-start" is extraordinarly useful because it log the complete
+ # configuration at INFO level, including defaults and overrides, so it s worth
+ # putting at the very top.
+ #
+ # Put the following in your conf/logback.xml file:
+ #
+ #
+ #
+ # And then uncomment this line to debug the configuration.
+ #
+ #log-config-on-start = true
+}
+
+## Secret key
+# http://www.playframework.com/documentation/latest/ApplicationSecret
+# ~~~~~
+# The secret key is used to sign Play's session cookie.
+# This must be changed for production, but we don't recommend you change it in this file.
+play.http.secret.key = a-long-secret-to-calm-the-rage-of-the-entropy-gods
+
+## Modules
+# https://www.playframework.com/documentation/latest/Modules
+# ~~~~~
+# Control which modules are loaded when Play starts. Note that modules are
+# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
+# Please see https://www.playframework.com/documentation/latest/GlobalSettings
+# for more information.
+#
+# You can also extend Play functionality by using one of the publically available
+# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
+play.modules {
+ # By default, Play will load any class called Module that is defined
+ # in the root package (the "app" directory), or you can define them
+ # explicitly below.
+ # If there are any built-in modules that you want to enable, you can list them here.
+ #enabled += my.application.Module
+
+ # If there are any built-in modules that you want to disable, you can list them here.
+ #disabled += ""
+}
+
+## IDE
+# https://www.playframework.com/documentation/latest/IDE
+# ~~~~~
+# Depending on your IDE, you can add a hyperlink for errors that will jump you
+# directly to the code location in the IDE in dev mode. The following line makes
+# use of the IntelliJ IDEA REST interface:
+#play.editor="http://localhost:63342/api/file/?file=%s&line=%s"
+
+## Internationalisation
+# https://www.playframework.com/documentation/latest/JavaI18N
+# https://www.playframework.com/documentation/latest/ScalaI18N
+# ~~~~~
+# Play comes with its own i18n settings, which allow the user's preferred language
+# to map through to internal messages, or allow the language to be stored in a cookie.
+play.i18n {
+ # The application languages
+ langs = [ "en" ]
+
+ # Whether the language cookie should be secure or not
+ #langCookieSecure = true
+
+ # Whether the HTTP only attribute of the cookie should be set to true
+ #langCookieHttpOnly = true
+}
+
+## Play HTTP settings
+# ~~~~~
+play.http {
+ ## Router
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # Define the Router object to use for this application.
+ # This router will be looked up first when the application is starting up,
+ # so make sure this is the entry point.
+ # Furthermore, it's assumed your route file is named properly.
+ # So for an application router like `my.application.Router`,
+ # you may need to define a router file `conf/my.application.routes`.
+ # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
+ #router = my.application.Router
+
+ ## Action Creator
+ # https://www.playframework.com/documentation/latest/JavaActionCreator
+ # ~~~~~
+ #actionCreator = null
+
+ ## ErrorHandler
+ # https://www.playframework.com/documentation/latest/JavaRouting
+ # https://www.playframework.com/documentation/latest/ScalaRouting
+ # ~~~~~
+ # If null, will attempt to load a class called ErrorHandler in the root package,
+ #errorHandler = null
+
+ ## Session & Flash
+ # https://www.playframework.com/documentation/latest/JavaSessionFlash
+ # https://www.playframework.com/documentation/latest/ScalaSessionFlash
+ # ~~~~~
+ session {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+
+ # Sets the max-age field of the cookie to 5 minutes.
+ # NOTE: this only sets when the browser will discard the cookie. Play will consider any
+ # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
+ # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
+ #maxAge = 300
+
+ # Sets the domain on the session cookie.
+ #domain = "example.com"
+ }
+
+ flash {
+ # Sets the cookie to be sent only over HTTPS.
+ #secure = true
+
+ # Sets the cookie to be accessed only by the server.
+ #httpOnly = true
+ }
+}
+
+## Netty Provider
+# https://www.playframework.com/documentation/latest/SettingsNetty
+# ~~~~~
+play.server.netty {
+ # Whether the Netty wire should be logged
+ log.wire = true
+
+ # If you run Play on Linux, you can use Netty's native socket transport
+ # for higher performance with less garbage.
+ transport = "native"
+}
+
+## WS (HTTP Client)
+# https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS
+# ~~~~~
+# The HTTP client primarily used for REST APIs. The default client can be
+# configured directly, but you can also create different client instances
+# with customized settings. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += ws // or javaWs if using java
+#
+play.ws {
+ # Sets HTTP requests not to follow 302 requests
+ #followRedirects = false
+
+ # Sets the maximum number of open HTTP connections for the client.
+ #ahc.maxConnectionsTotal = 50
+
+ ## WS SSL
+ # https://www.playframework.com/documentation/latest/WsSSL
+ # ~~~~~
+ ssl {
+ # Configuring HTTPS with Play WS does not require programming. You can
+ # set up both trustManager and keyManager for mutual authentication, and
+ # turn on JSSE debugging in development with a reload.
+ #debug.handshake = true
+ #trustManager = {
+ # stores = [
+ # { type = "JKS", path = "exampletrust.jks" }
+ # ]
+ #}
+ }
+}
+
+## Cache
+# https://www.playframework.com/documentation/latest/JavaCache
+# https://www.playframework.com/documentation/latest/ScalaCache
+# ~~~~~
+# Play comes with an integrated cache API that can reduce the operational
+# overhead of repeated requests. You must enable this by adding to build.sbt:
+#
+# libraryDependencies += cache
+#
+play.cache {
+ # If you want to bind several caches, you can bind the individually
+ #bindCaches = ["db-cache", "user-cache", "session-cache"]
+}
+
+## Filter Configuration
+# https://www.playframework.com/documentation/latest/Filters
+# ~~~~~
+# There are a number of built-in filters that can be enabled and configured
+# to give Play greater security.
+#
+play.filters {
+
+ # Enabled filters are run automatically against Play.
+ # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
+ enabled = []
+
+ # Disabled filters remove elements from the enabled list.
+ # disabled += filters.CSRFFilter
+
+
+ ## CORS filter configuration
+ # https://www.playframework.com/documentation/latest/CorsFilter
+ # ~~~~~
+ # CORS is a protocol that allows web applications to make requests from the browser
+ # across different domains.
+ # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
+ # dependencies on CORS settings.
+ cors {
+ # Filter paths by a whitelist of path prefixes
+ #pathPrefixes = ["/some/path", ...]
+
+ # The allowed origins. If null, all origins are allowed.
+ #allowedOrigins = ["http://www.example.com"]
+
+ # The allowed HTTP methods. If null, all methods are allowed
+ #allowedHttpMethods = ["GET", "POST"]
+ }
+
+ ## Security headers filter configuration
+ # https://www.playframework.com/documentation/latest/SecurityHeaders
+ # ~~~~~
+ # Defines security headers that prevent XSS attacks.
+ # If enabled, then all options are set to the below configuration by default:
+ headers {
+ # The X-Frame-Options header. If null, the header is not set.
+ #frameOptions = "DENY"
+
+ # The X-XSS-Protection header. If null, the header is not set.
+ #xssProtection = "1; mode=block"
+
+ # The X-Content-Type-Options header. If null, the header is not set.
+ #contentTypeOptions = "nosniff"
+
+ # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
+ #permittedCrossDomainPolicies = "master-only"
+
+ # The Content-Security-Policy header. If null, the header is not set.
+ #contentSecurityPolicy = "default-src 'self'"
+ }
+
+ ## Allowed hosts filter configuration
+ # https://www.playframework.com/documentation/latest/AllowedHostsFilter
+ # ~~~~~
+ # Play provides a filter that lets you configure which hosts can access your application.
+ # This is useful to prevent cache poisoning attacks.
+ hosts {
+ # Allow requests to example.com, its subdomains, and localhost:9000.
+ #allowed = [".example.com", "localhost:9000"]
+ }
+}
+
+# Learning-Service Configuration
+content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit"]
+
+# Cassandra Configuration
+content.keyspace.name=content_store
+content.keyspace.table=content_data
+#TODO: Add Configuration for assessment. e.g: question_data
+orchestrator.keyspace.name=script_store
+orchestrator.keyspace.table=script_data
+cassandra.lp.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+cassandra.lpa.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+
+# Redis Configuration
+redis.host=localhost
+redis.port=6379
+redis.maxConnections=128
+
+#Condition to enable publish locally
+content.publish_task.enabled=true
+
+#directory location where store unzip file
+dist.directory=/data/tmp/dist/
+output.zipfile=/data/tmp/story.zip
+source.folder=/data/tmp/temp2/
+save.directory=/data/tmp/temp/
+
+# Content 2 vec analytics URL
+CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec"
+
+# FOR CONTENT WORKFLOW PIPELINE (CWP)
+
+#--Content Workflow Pipeline Mode
+OPERATION_MODE=TEST
+
+#--Maximum Content Package File Size Limit in Bytes (50 MB)
+MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800
+
+#--Maximum Asset File Size Limit in Bytes (20 MB)
+MAX_ASSET_FILE_SIZE_LIMIT=20971520
+
+#--No of Retry While File Download Fails
+RETRY_ASSET_DOWNLOAD_COUNT=1
+
+#Google-vision-API
+google.vision.tagging.enabled = false
+
+#Orchestrator env properties
+env="https://dev.ekstep.in/api/learning"
+
+#Current environment
+cloud_storage.env=dev
+
+
+#Folder configuration
+cloud_storage.content.folder=content
+cloud_storage.asset.folder=assets
+cloud_storage.artefact.folder=artifact
+cloud_storage.bundle.folder=bundle
+cloud_storage.media.folder=media
+cloud_storage.ecar.folder=ecar_files
+
+# Media download configuration
+content.media.base.url="https://dev.open-sunbird.org"
+plugin.media.base.url="https://dev.open-sunbird.org"
+
+# Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+# Elasticsearch properties
+search.es_conn_info="localhost:9200"
+search.fields.query=["name^100","title^100","lemma^100","code^100","tags^100","domain","subject","description^10","keywords^25","ageGroup^10","filter^10","theme^10","genre^10","objects^25","contentType^100","language^200","teachingMode^25","skills^10","learningObjective^10","curriculum^100","gradeLevel^100","developer^100","attributions^10","owner^50","text","words","releaseNotes"]
+search.fields.date=["lastUpdatedOn","createdOn","versionDate","lastSubmittedOn","lastPublishedOn"]
+search.batch.size=500
+search.connection.timeout=30
+platform-api-url="http://localhost:8080/language-service"
+MAX_ITERATION_COUNT_FOR_SAMZA_JOB=2
+
+
+# DIAL Code Configuration
+dialcode.keyspace.name="dialcode_store"
+dialcode.keyspace.table="dial_code"
+dialcode.max_count=1000
+
+# System Configuration
+system.config.keyspace.name="dialcode_store"
+system.config.table="system_config"
+
+#Publisher Configuration
+publisher.keyspace.name="dialcode_store"
+publisher.keyspace.table="publisher"
+
+#DIAL Code Generator Configuration
+dialcode.strip.chars="0"
+dialcode.length=6.0
+dialcode.large.prime_number=1679979167
+
+#DIAL Code ElasticSearch Configuration
+dialcode.index=true
+dialcode.object_type="DialCode"
+
+framework.max_term_creation_limit=200
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+# Kafka configuration details
+kafka.topics.instruction="local.learning.job.request"
+kafka.urls="localhost:9092"
+
+#Youtube Standard Licence Validation
+learning.content.youtube.validate.license=true
+learning.content.youtube.application.name=fetch-youtube-license
+youtube.license.regex.pattern=["\\?vi?=([^&]*)", "watch\\?.*v=([^&]*)", "(?:embed|vi?)/([^/?]*)","^([A-Za-z0-9\\-\\_]*)"]
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+telemetry.search.topn=5
+
+installation.id=ekstep
+
+
+channel.default="in.ekstep"
+
+# DialCode Link API Config
+learning.content.link_dialcode_validation=true
+dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/search"
+dialcode.api.authorization=auth_key
+
+# Language-Code Configuration
+platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+
+# Kafka send event to topic enable
+kafka.topic.send.enable=false
+
+learning.valid_license=["creativeCommon"]
+learning.service_provider=["youtube"]
+
+stream.mime.type=video/mp4
+compositesearch.index.name="compositesearch"
+
+hierarchy.keyspace.name=hierarchy_store
+content.hierarchy.table=content_hierarchy
+framework.hierarchy.table=framework_hierarchy
+
+# Kafka topic for definition update event.
+kafka.topic.system.command="dev.system.command"
+
+learning.reserve_dialcode.content_type=["TextBook"]
+# restrict.metadata.objectTypes=["Content", "ContentImage", "AssessmentItem", "Channel", "Framework", "Category", "CategoryInstance", "Term"]
+
+#restrict.metadata.objectTypes="Content,ContentImage"
+
+publish.collection.fullecar.disable=true
+
+# Consistency Level for Multi Node Cassandra cluster
+cassandra.lp.consistency.level=QUORUM
+
+
+
+
+content.nested.fields="badgeAssertions,targets,badgeAssociations"
+
+content.cache.ttl=86400
+content.cache.enable=true
+collection.cache.enable=true
+content.discard.status=["Draft","FlagDraft"]
+
+framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
+framework.cache.ttl=86400
+framework.cache.read=true
+
+
+# Max size(width/height) of thumbnail in pixels
+max.thumbnail.size.pixels=150
+
+schema.base_path="../../schemas/"
+content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"]
+
+collection.keyspace = "hierarchy_store"
+content.keyspace = "content_store"
+
+collection.image.migration.enabled=true
+
+
+
+# This is added to handle large artifacts sizes differently
+content.artifact.size.for_online=209715200
+
+contentTypeToPrimaryCategory {
+ ClassroomTeachingVideo: "Explanation Content"
+ ConceptMap: "Learning Resource"
+ Course: "Course"
+ CuriosityQuestionSet: "Practice Question Set"
+ eTextBook: "eTextbook"
+ ExperientialResource: "Learning Resource"
+ ExplanationResource: "Explanation Content"
+ ExplanationVideo: "Explanation Content"
+ FocusSpot: "Teacher Resource"
+ LearningOutcomeDefinition: "Teacher Resource"
+ MarkingSchemeRubric: "Teacher Resource"
+ PedagogyFlow: "Teacher Resource"
+ PracticeQuestionSet: "Practice Question Set"
+ PracticeResource: "Practice Question Set"
+ SelfAssess: "Course Assessment"
+ TeachingMethod: "Teacher Resource"
+ TextBook: "Digital Textbook"
+ Collection: "Content Playlist"
+ ExplanationReadingMaterial: "Learning Resource"
+ LearningActivity: "Learning Resource"
+ LessonPlan: "Content Playlist"
+ LessonPlanResource: "Teacher Resource"
+ PreviousBoardExamPapers: "Learning Resource"
+ TVLesson: "Explanation Content"
+ OnboardingResource: "Learning Resource"
+ ReadingMaterial: "Learning Resource"
+ Template: "Template"
+ Asset: "Asset"
+ Plugin: "Plugin"
+ LessonPlanUnit: "Lesson Plan Unit"
+ CourseUnit: "Course Unit"
+ TextBookUnit: "Textbook Unit"
+}
+
+resourceTypeToPrimaryCategory {
+ Learn: "Learning Resource"
+ Read: "Learning Resource"
+ Practice: "Learning Resource"
+ Teach: "Teacher Resource"
+ Test: "Learning Resource"
+ Experiment: "Learning Resource"
+ LessonPlan: "Teacher Resource"
+}
+
+mimeTypeToPrimaryCategory {
+ "application/vnd.ekstep.h5p-archive": ["Learning Resource"]
+ "application/vnd.ekstep.html-archive": ["Learning Resource"]
+ "application/vnd.android.package-archive": ["Learning Resource"]
+ "video/webm": ["Explanation Content"]
+ "video/x-youtube": ["Explanation Content"]
+ "video/mp4": ["Explanation Content"]
+ "application/pdf": ["Learning Resource", "Teacher Resource"]
+ "application/epub": ["Learning Resource", "Teacher Resource"]
+ "application/vnd.ekstep.ecml-archive": ["Learning Resource", "Teacher Resource"]
+ "text/x-url": ["Learnin Resource", "Teacher Resource"]
+}
+
+objectcategorydefinition.keyspace=category_store
diff --git a/content-api/hierarchy-manager/src/test/resources/cassandra-unit.yaml b/content-api/hierarchy-manager/src/test/resources/cassandra-unit.yaml
new file mode 100755
index 000000000..a965a8fe5
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/resources/cassandra-unit.yaml
@@ -0,0 +1,590 @@
+# Cassandra storage config YAML
+
+# NOTE:
+# See http://wiki.apache.org/cassandra/StorageConfiguration for
+# full explanations of configuration directives
+# /NOTE
+
+# The name of the cluster. This is mainly used to prevent machines in
+# one logical cluster from joining another.
+cluster_name: 'Test Cluster'
+
+# You should always specify InitialToken when setting up a production
+# cluster for the first time, and often when adding capacity later.
+# The principle is that each node should be given an equal slice of
+# the token ring; see http://wiki.apache.org/cassandra/Operations
+# for more details.
+#
+# If blank, Cassandra will request a token bisecting the range of
+# the heaviest-loaded existing node. If there is no load information
+# available, such as is the case with a new cluster, it will pick
+# a random token, which will lead to hot spots.
+#initial_token:
+
+# See http://wiki.apache.org/cassandra/HintedHandoff
+hinted_handoff_enabled: true
+# this defines the maximum amount of time a dead host will have hints
+# generated. After it has been dead this long, new hints for it will not be
+# created until it has been seen alive and gone down again.
+max_hint_window_in_ms: 10800000 # 3 hours
+# Maximum throttle in KBs per second, per delivery thread. This will be
+# reduced proportionally to the number of nodes in the cluster. (If there
+# are two nodes in the cluster, each delivery thread will use the maximum
+# rate; if there are three, each will throttle to half of the maximum,
+# since we expect two nodes to be delivering hints simultaneously.)
+hinted_handoff_throttle_in_kb: 1024
+# Number of threads with which to deliver hints;
+# Consider increasing this number when you have multi-dc deployments, since
+# cross-dc handoff tends to be slower
+max_hints_delivery_threads: 2
+
+hints_directory: target/embeddedCassandra/hints
+
+# The following setting populates the page cache on memtable flush and compaction
+# WARNING: Enable this setting only when the whole node's data fits in memory.
+# Defaults to: false
+# populate_io_cache_on_flush: false
+
+# Authentication backend, implementing IAuthenticator; used to identify users
+# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator,
+# PasswordAuthenticator}.
+#
+# - AllowAllAuthenticator performs no checks - set it to disable authentication.
+# - PasswordAuthenticator relies on username/password pairs to authenticate
+# users. It keeps usernames and hashed passwords in system_auth.credentials table.
+# Please increase system_auth keyspace replication factor if you use this authenticator.
+authenticator: AllowAllAuthenticator
+
+# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
+# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer,
+# CassandraAuthorizer}.
+#
+# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.
+# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please
+# increase system_auth keyspace replication factor if you use this authorizer.
+authorizer: AllowAllAuthorizer
+
+# Validity period for permissions cache (fetching permissions can be an
+# expensive operation depending on the authorizer, CassandraAuthorizer is
+# one example). Defaults to 2000, set to 0 to disable.
+# Will be disabled automatically for AllowAllAuthorizer.
+permissions_validity_in_ms: 2000
+
+
+# The partitioner is responsible for distributing rows (by key) across
+# nodes in the cluster. Any IPartitioner may be used, including your
+# own as long as it is on the classpath. Out of the box, Cassandra
+# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner
+# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}.
+#
+# - RandomPartitioner distributes rows across the cluster evenly by md5.
+# This is the default prior to 1.2 and is retained for compatibility.
+# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128
+# Hash Function instead of md5. When in doubt, this is the best option.
+# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows
+# scanning rows in key order, but the ordering can generate hot spots
+# for sequential insertion workloads.
+# - OrderPreservingPartitioner is an obsolete form of BOP, that stores
+# - keys in a less-efficient format and only works with keys that are
+# UTF8-encoded Strings.
+# - CollatingOPP collates according to EN,US rules rather than lexical byte
+# ordering. Use this as an example if you need custom collation.
+#
+# See http://wiki.apache.org/cassandra/Operations for more on
+# partitioners and token selection.
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+
+# directories where Cassandra should store data on disk.
+data_file_directories:
+ - target/embeddedCassandra/data
+
+# commit log
+commitlog_directory: target/embeddedCassandra/commitlog
+
+cdc_raw_directory: target/embeddedCassandra/cdc
+
+# policy for data disk failures:
+# stop: shut down gossip and Thrift, leaving the node effectively dead, but
+# can still be inspected via JMX.
+# best_effort: stop using the failed disk and respond to requests based on
+# remaining available sstables. This means you WILL see obsolete
+# data at CL.ONE!
+# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra
+disk_failure_policy: stop
+
+
+# Maximum size of the key cache in memory.
+#
+# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the
+# minimum, sometimes more. The key cache is fairly tiny for the amount of
+# time it saves, so it's worthwhile to use it at large numbers.
+# The row cache saves even more time, but must store the whole values of
+# its rows, so it is extremely space-intensive. It's best to only use the
+# row cache if you have hot rows or static rows.
+#
+# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.
+#
+# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache.
+key_cache_size_in_mb:
+
+# Duration in seconds after which Cassandra should
+# safe the keys cache. Caches are saved to saved_caches_directory as
+# specified in this configuration file.
+#
+# Saved caches greatly improve cold-start speeds, and is relatively cheap in
+# terms of I/O for the key cache. Row cache saving is much more expensive and
+# has limited use.
+#
+# Default is 14400 or 4 hours.
+key_cache_save_period: 14400
+
+# Number of keys from the key cache to save
+# Disabled by default, meaning all keys are going to be saved
+# key_cache_keys_to_save: 100
+
+# Maximum size of the row cache in memory.
+# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.
+#
+# Default value is 0, to disable row caching.
+row_cache_size_in_mb: 0
+
+# Duration in seconds after which Cassandra should
+# safe the row cache. Caches are saved to saved_caches_directory as specified
+# in this configuration file.
+#
+# Saved caches greatly improve cold-start speeds, and is relatively cheap in
+# terms of I/O for the key cache. Row cache saving is much more expensive and
+# has limited use.
+#
+# Default is 0 to disable saving the row cache.
+row_cache_save_period: 0
+
+# Number of keys from the row cache to save
+# Disabled by default, meaning all keys are going to be saved
+# row_cache_keys_to_save: 100
+
+# saved caches
+saved_caches_directory: target/embeddedCassandra/saved_caches
+
+# commitlog_sync may be either "periodic" or "batch."
+# When in batch mode, Cassandra won't ack writes until the commit log
+# has been fsynced to disk. It will wait up to
+# commitlog_sync_batch_window_in_ms milliseconds for other writes, before
+# performing the sync.
+#
+# commitlog_sync: batch
+# commitlog_sync_batch_window_in_ms: 50
+#
+# the other option is "periodic" where writes may be acked immediately
+# and the CommitLog is simply synced every commitlog_sync_period_in_ms
+# milliseconds.
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+
+# The size of the individual commitlog file segments. A commitlog
+# segment may be archived, deleted, or recycled once all the data
+# in it (potentially from each columnfamily in the system) has been
+# flushed to sstables.
+#
+# The default size is 32, which is almost always fine, but if you are
+# archiving commitlog segments (see commitlog_archiving.properties),
+# then you probably want a finer granularity of archiving; 8 or 16 MB
+# is reasonable.
+commitlog_segment_size_in_mb: 32
+
+# any class that implements the SeedProvider interface and has a
+# constructor that takes a Map of parameters will do.
+seed_provider:
+ # Addresses of hosts that are deemed contact points.
+ # Cassandra nodes use this list of hosts to find each other and learn
+ # the topology of the ring. You must change this if you are running
+ # multiple nodes!
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ # seeds is actually a comma-delimited list of addresses.
+ # Ex: ",,"
+ - seeds: "127.0.0.1"
+
+
+# For workloads with more data than can fit in memory, Cassandra's
+# bottleneck will be reads that need to fetch data from
+# disk. "concurrent_reads" should be set to (16 * number_of_drives) in
+# order to allow the operations to enqueue low enough in the stack
+# that the OS and drives can reorder them.
+#
+# On the other hand, since writes are almost never IO bound, the ideal
+# number of "concurrent_writes" is dependent on the number of cores in
+# your system; (8 * number_of_cores) is a good rule of thumb.
+concurrent_reads: 32
+concurrent_writes: 32
+
+# Total memory to use for memtables. Cassandra will flush the largest
+# memtable when this much memory is used.
+# If omitted, Cassandra will set it to 1/3 of the heap.
+# memtable_total_space_in_mb: 2048
+
+# Total space to use for commitlogs.
+# If space gets above this value (it will round up to the next nearest
+# segment multiple), Cassandra will flush every dirty CF in the oldest
+# segment and remove it.
+# commitlog_total_space_in_mb: 4096
+
+# This sets the amount of memtable flush writer threads. These will
+# be blocked by disk io, and each one will hold a memtable in memory
+# while blocked. If you have a large heap and many data directories,
+# you can increase this value for better flush performance.
+# By default this will be set to the amount of data directories defined.
+#memtable_flush_writers: 1
+
+# the number of full memtables to allow pending flush, that is,
+# waiting for a writer thread. At a minimum, this should be set to
+# the maximum number of secondary indexes created on a single CF.
+#memtable_flush_queue_size: 4
+
+# Whether to, when doing sequential writing, fsync() at intervals in
+# order to force the operating system to flush the dirty
+# buffers. Enable this to avoid sudden dirty buffer flushing from
+# impacting read latencies. Almost always a good idea on SSD:s; not
+# necessarily on platters.
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+
+# TCP port, for commands and data
+storage_port: 0
+
+# SSL port, for encrypted communication. Unused unless enabled in
+# encryption_options
+ssl_storage_port: 7011
+
+# Address to bind to and tell other Cassandra nodes to connect to. You
+# _must_ change this if you want multiple nodes to be able to
+# communicate!
+#
+# Leaving it blank leaves it up to InetAddress.getLocalHost(). This
+# will always do the Right Thing *if* the node is properly configured
+# (hostname, name resolution, etc), and the Right Thing is to use the
+# address associated with the hostname (it might not be).
+#
+# Setting this to 0.0.0.0 is always wrong.
+listen_address: 127.0.0.1
+
+start_native_transport: true
+# port for the CQL native transport to listen for clients on
+native_transport_port: 9042
+
+# Whether to start the thrift rpc server.
+start_rpc: true
+
+# Address to broadcast to other Cassandra nodes
+# Leaving this blank will set it to the same value as listen_address
+# broadcast_address: 1.2.3.4
+
+# The address to bind the Thrift RPC service to -- clients connect
+# here. Unlike ListenAddress above, you *can* specify 0.0.0.0 here if
+# you want Thrift to listen on all interfaces.
+#
+# Leaving this blank has the same effect it does for ListenAddress,
+# (i.e. it will be based on the configured hostname of the node).
+rpc_address: localhost
+# port for Thrift to listen for clients on
+rpc_port: 0
+
+# enable or disable keepalive on rpc connections
+rpc_keepalive: true
+
+# Cassandra provides three options for the RPC Server:
+#
+# sync -> One connection per thread in the rpc pool (see below).
+# For a very large number of clients, memory will be your limiting
+# factor; on a 64 bit JVM, 128KB is the minimum stack size per thread.
+# Connection pooling is very, very strongly recommended.
+#
+# async -> Nonblocking server implementation with one thread to serve
+# rpc connections. This is not recommended for high throughput use
+# cases. Async has been tested to be about 50% slower than sync
+# or hsha and is deprecated: it will be removed in the next major release.
+#
+# hsha -> Stands for "half synchronous, half asynchronous." The rpc thread pool
+# (see below) is used to manage requests, but the threads are multiplexed
+# across the different clients.
+#
+# The default is sync because on Windows hsha is about 30% slower. On Linux,
+# sync/hsha performance is about the same, with hsha of course using less memory.
+rpc_server_type: sync
+
+# Uncomment rpc_min|max|thread to set request pool size.
+# You would primarily set max for the sync server to safeguard against
+# misbehaved clients; if you do hit the max, Cassandra will block until one
+# disconnects before accepting more. The defaults for sync are min of 16 and max
+# unlimited.
+#
+# For the Hsha server, the min and max both default to quadruple the number of
+# CPU cores.
+#
+# This configuration is ignored by the async server.
+#
+# rpc_min_threads: 16
+# rpc_max_threads: 2048
+
+# uncomment to set socket buffer sizes on rpc connections
+# rpc_send_buff_size_in_bytes:
+# rpc_recv_buff_size_in_bytes:
+
+# Frame size for thrift (maximum field length).
+# 0 disables TFramedTransport in favor of TSocket. This option
+# is deprecated; we strongly recommend using Framed mode.
+thrift_framed_transport_size_in_mb: 15
+
+# The max length of a thrift message, including all fields and
+# internal thrift overhead.
+thrift_max_message_length_in_mb: 16
+
+# Set to true to have Cassandra create a hard link to each sstable
+# flushed or streamed locally in a backups/ subdirectory of the
+# Keyspace data. Removing these links is the operator's
+# responsibility.
+incremental_backups: false
+
+# Whether or not to take a snapshot before each compaction. Be
+# careful using this option, since Cassandra won't clean up the
+# snapshots for you. Mostly useful if you're paranoid when there
+# is a data format change.
+snapshot_before_compaction: false
+
+# Whether or not a snapshot is taken of the data before keyspace truncation
+# or dropping of column families. The STRONGLY advised default of true
+# should be used to provide data safety. If you set this flag to false, you will
+# lose data on truncation or drop.
+auto_snapshot: false
+
+# Add column indexes to a row after its contents reach this size.
+# Increase if your column values are large, or if you have a very large
+# number of columns. The competing causes are, Cassandra has to
+# deserialize this much of the row to read a single column, so you want
+# it to be small - at least if you do many partial-row reads - but all
+# the index data is read for each access, so you don't want to generate
+# that wastefully either.
+column_index_size_in_kb: 64
+
+# Size limit for rows being compacted in memory. Larger rows will spill
+# over to disk and use a slower two-pass compaction process. A message
+# will be logged specifying the row key.
+#in_memory_compaction_limit_in_mb: 64
+
+# Number of simultaneous compactions to allow, NOT including
+# validation "compactions" for anti-entropy repair. Simultaneous
+# compactions can help preserve read performance in a mixed read/write
+# workload, by mitigating the tendency of small sstables to accumulate
+# during a single long running compactions. The default is usually
+# fine and if you experience problems with compaction running too
+# slowly or too fast, you should look at
+# compaction_throughput_mb_per_sec first.
+#
+# This setting has no effect on LeveledCompactionStrategy.
+#
+# concurrent_compactors defaults to the number of cores.
+# Uncomment to make compaction mono-threaded, the pre-0.8 default.
+#concurrent_compactors: 1
+
+# Multi-threaded compaction. When enabled, each compaction will use
+# up to one thread per core, plus one thread per sstable being merged.
+# This is usually only useful for SSD-based hardware: otherwise,
+# your concern is usually to get compaction to do LESS i/o (see:
+# compaction_throughput_mb_per_sec), not more.
+#multithreaded_compaction: false
+
+# Throttles compaction to the given total throughput across the entire
+# system. The faster you insert data, the faster you need to compact in
+# order to keep the sstable count down, but in general, setting this to
+# 16 to 32 times the rate you are inserting data is more than sufficient.
+# Setting this to 0 disables throttling. Note that this account for all types
+# of compaction, including validation compaction.
+compaction_throughput_mb_per_sec: 16
+
+# Track cached row keys during compaction, and re-cache their new
+# positions in the compacted sstable. Disable if you use really large
+# key caches.
+#compaction_preheat_key_cache: true
+
+# Throttles all outbound streaming file transfers on this node to the
+# given total throughput in Mbps. This is necessary because Cassandra does
+# mostly sequential IO when streaming data during bootstrap or repair, which
+# can lead to saturating the network connection and degrading rpc performance.
+# When unset, the default is 200 Mbps or 25 MB/s.
+# stream_throughput_outbound_megabits_per_sec: 200
+
+# How long the coordinator should wait for read operations to complete
+read_request_timeout_in_ms: 5000
+# How long the coordinator should wait for seq or index scans to complete
+range_request_timeout_in_ms: 10000
+# How long the coordinator should wait for writes to complete
+write_request_timeout_in_ms: 2000
+# How long a coordinator should continue to retry a CAS operation
+# that contends with other proposals for the same row
+cas_contention_timeout_in_ms: 1000
+# How long the coordinator should wait for truncates to complete
+# (This can be much longer, because unless auto_snapshot is disabled
+# we need to flush first so we can snapshot before removing the data.)
+truncate_request_timeout_in_ms: 60000
+# The default timeout for other, miscellaneous operations
+request_timeout_in_ms: 10000
+
+# Enable operation timeout information exchange between nodes to accurately
+# measure request timeouts. If disabled, replicas will assume that requests
+# were forwarded to them instantly by the coordinator, which means that
+# under overload conditions we will waste that much extra time processing
+# already-timed-out requests.
+#
+# Warning: before enabling this property make sure to ntp is installed
+# and the times are synchronized between the nodes.
+cross_node_timeout: false
+
+# Enable socket timeout for streaming operation.
+# When a timeout occurs during streaming, streaming is retried from the start
+# of the current file. This _can_ involve re-streaming an important amount of
+# data, so you should avoid setting the value too low.
+# Default value is 0, which never timeout streams.
+# streaming_socket_timeout_in_ms: 0
+
+# phi value that must be reached for a host to be marked down.
+# most users should never need to adjust this.
+# phi_convict_threshold: 8
+
+# endpoint_snitch -- Set this to a class that implements
+# IEndpointSnitch. The snitch has two functions:
+# - it teaches Cassandra enough about your network topology to route
+# requests efficiently
+# - it allows Cassandra to spread replicas around your cluster to avoid
+# correlated failures. It does this by grouping machines into
+# "datacenters" and "racks." Cassandra will do its best not to have
+# more than one replica on the same "rack" (which may not actually
+# be a physical location)
+#
+# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER,
+# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS
+# ARE PLACED.
+#
+# Out of the box, Cassandra provides
+# - SimpleSnitch:
+# Treats Strategy order as proximity. This improves cache locality
+# when disabling read repair, which can further improve throughput.
+# Only appropriate for single-datacenter deployments.
+# - PropertyFileSnitch:
+# Proximity is determined by rack and data center, which are
+# explicitly configured in cassandra-topology.properties.
+# - RackInferringSnitch:
+# Proximity is determined by rack and data center, which are
+# assumed to correspond to the 3rd and 2nd octet of each node's
+# IP address, respectively. Unless this happens to match your
+# deployment conventions (as it did Facebook's), this is best used
+# as an example of writing a custom Snitch class.
+# - Ec2Snitch:
+# Appropriate for EC2 deployments in a single Region. Loads Region
+# and Availability Zone information from the EC2 API. The Region is
+# treated as the Datacenter, and the Availability Zone as the rack.
+# Only private IPs are used, so this will not work across multiple
+# Regions.
+# - Ec2MultiRegionSnitch:
+# Uses public IPs as broadcast_address to allow cross-region
+# connectivity. (Thus, you should set seed addresses to the public
+# IP as well.) You will need to open the storage_port or
+# ssl_storage_port on the public IP firewall. (For intra-Region
+# traffic, Cassandra will switch to the private IP after
+# establishing a connection.)
+#
+# You can use a custom Snitch by setting this to the full class name
+# of the snitch, which will be assumed to be on your classpath.
+endpoint_snitch: SimpleSnitch
+
+# controls how often to perform the more expensive part of host score
+# calculation
+dynamic_snitch_update_interval_in_ms: 100
+# controls how often to reset all host scores, allowing a bad host to
+# possibly recover
+dynamic_snitch_reset_interval_in_ms: 600000
+# if set greater than zero and read_repair_chance is < 1.0, this will allow
+# 'pinning' of replicas to hosts in order to increase cache capacity.
+# The badness threshold will control how much worse the pinned host has to be
+# before the dynamic snitch will prefer other replicas over it. This is
+# expressed as a double which represents a percentage. Thus, a value of
+# 0.2 means Cassandra would continue to prefer the static snitch values
+# until the pinned host was 20% worse than the fastest.
+dynamic_snitch_badness_threshold: 0.1
+
+# request_scheduler -- Set this to a class that implements
+# RequestScheduler, which will schedule incoming client requests
+# according to the specific policy. This is useful for multi-tenancy
+# with a single Cassandra cluster.
+# NOTE: This is specifically for requests from the client and does
+# not affect inter node communication.
+# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place
+# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of
+# client requests to a node with a separate queue for each
+# request_scheduler_id. The scheduler is further customized by
+# request_scheduler_options as described below.
+request_scheduler: org.apache.cassandra.scheduler.NoScheduler
+
+# Scheduler Options vary based on the type of scheduler
+# NoScheduler - Has no options
+# RoundRobin
+# - throttle_limit -- The throttle_limit is the number of in-flight
+# requests per client. Requests beyond
+# that limit are queued up until
+# running requests can complete.
+# The value of 80 here is twice the number of
+# concurrent_reads + concurrent_writes.
+# - default_weight -- default_weight is optional and allows for
+# overriding the default which is 1.
+# - weights -- Weights are optional and will default to 1 or the
+# overridden default_weight. The weight translates into how
+# many requests are handled during each turn of the
+# RoundRobin, based on the scheduler id.
+#
+# request_scheduler_options:
+# throttle_limit: 80
+# default_weight: 5
+# weights:
+# Keyspace1: 1
+# Keyspace2: 5
+
+# request_scheduler_id -- An identifer based on which to perform
+# the request scheduling. Currently the only valid option is keyspace.
+# request_scheduler_id: keyspace
+
+# index_interval controls the sampling of entries from the primrary
+# row index in terms of space versus time. The larger the interval,
+# the smaller and less effective the sampling will be. In technicial
+# terms, the interval coresponds to the number of index entries that
+# are skipped between taking each sample. All the sampled entries
+# must fit in memory. Generally, a value between 128 and 512 here
+# coupled with a large key cache size on CFs results in the best trade
+# offs. This value is not often changed, however if you have many
+# very small rows (many to an OS page), then increasing this will
+# often lower memory usage without a impact on performance.
+index_interval: 128
+
+# Enable or disable inter-node encryption
+# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that
+# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher
+# suite for authentication, key exchange and encryption of the actual data transfers.
+# NOTE: No custom encryption options are enabled at the moment
+# The available internode options are : all, none, dc, rack
+#
+# If set to dc cassandra will encrypt the traffic between the DCs
+# If set to rack cassandra will encrypt the traffic between the racks
+#
+# The passwords used in these options must match the passwords used when generating
+# the keystore and truststore. For instructions on generating these files, see:
+# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore
+#
+encryption_options:
+ internode_encryption: none
+ keystore: conf/.keystore
+ keystore_password: cassandra
+ truststore: conf/.truststore
+ truststore_password: cassandra
+ # More advanced defaults below:
+ # protocol: TLS
+ # algorithm: SunX509
+ # store_type: JKS
+ # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA]
\ No newline at end of file
diff --git a/content-api/hierarchy-manager/src/test/resources/logback.xml b/content-api/hierarchy-manager/src/test/resources/logback.xml
new file mode 100644
index 000000000..73529d622
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/resources/logback.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ %d %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
new file mode 100644
index 000000000..8081f2397
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala
@@ -0,0 +1,118 @@
+package org.sunbird.managers
+
+import java.io.{File, IOException}
+
+import com.datastax.driver.core.{ResultSet, Session}
+import org.apache.commons.io.FileUtils
+import org.cassandraunit.utils.EmbeddedCassandraServerHelper
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.graphdb.factory.GraphDatabaseSettings.Connector.ConnectorType
+import org.neo4j.kernel.configuration.BoltConnector
+import org.scalatest.{AsyncFlatSpec, BeforeAndAfterAll, BeforeAndAfterEach, Matchers}
+import org.sunbird.cassandra.CassandraConnector
+import org.sunbird.common.Platform
+
+class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach {
+
+ var graphDb: GraphDatabaseService = null
+ var session: Session = null
+
+ private val script_1 = "CREATE KEYSPACE IF NOT EXISTS content_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val script_2 = "CREATE TABLE IF NOT EXISTS content_store.content_data (content_id text, last_updated_on timestamp,body blob,oldBody blob,stageIcons blob,PRIMARY KEY (content_id));"
+ private val script_5 = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val script_6 = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text, objectmetadata map, forms map ,PRIMARY KEY (identifier));"
+ private val script_7 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_8 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});"
+ private val script_9 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_content_all',{'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});"
+ private val script_10 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_11 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+ private val script_12 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});"
+
+ def setUpEmbeddedNeo4j(): Unit = {
+ if(null == graphDb) {
+ val bolt: BoltConnector = new BoltConnector("0")
+ println("GraphDB : " + Platform.config.getString("graph.dir"))
+ graphDb = new GraphDatabaseFactory()
+ .newEmbeddedDatabaseBuilder(new File(Platform.config.getString("graph.dir")))
+ .setConfig(bolt.`type`, ConnectorType.BOLT.name())
+ .setConfig(bolt.enabled, "true").setConfig(bolt.listen_address, "localhost:7687").newGraphDatabase
+ registerShutdownHook(graphDb)
+ }
+ }
+
+ private def registerShutdownHook(graphDb: GraphDatabaseService): Unit = {
+ Runtime.getRuntime.addShutdownHook(new Thread() {
+ override def run(): Unit = {
+ try {
+ tearEmbeddedNeo4JSetup
+ System.out.println("cleanup Done!!")
+ } catch {
+ case e: Exception =>
+ e.printStackTrace()
+ }
+ }
+ })
+ }
+
+
+ @throws[Exception]
+ private def tearEmbeddedNeo4JSetup(): Unit = {
+ if (null != graphDb) graphDb.shutdown
+ Thread.sleep(2000)
+ deleteEmbeddedNeo4j(new File(Platform.config.getString("graph.dir")))
+ }
+
+ private def deleteEmbeddedNeo4j(emDb: File): Unit = {
+ try{
+ FileUtils.deleteDirectory(emDb)
+ }catch{
+ case e: Exception =>
+ e.printStackTrace()
+ }
+ }
+
+
+ def setUpEmbeddedCassandra(): Unit = {
+ System.setProperty("cassandra.unsafesystem", "true")
+ EmbeddedCassandraServerHelper.startEmbeddedCassandra("/cassandra-unit.yaml", 100000L)
+ }
+
+ override def beforeAll(): Unit = {
+ tearEmbeddedNeo4JSetup()
+ setUpEmbeddedNeo4j()
+ setUpEmbeddedCassandra()
+ executeCassandraQuery(script_1, script_2, script_5, script_6, script_7, script_8, script_9, script_10, script_11, script_12)
+ }
+
+ override def afterAll(): Unit = {
+ tearEmbeddedNeo4JSetup()
+ if(null != session && !session.isClosed)
+ session.close()
+ EmbeddedCassandraServerHelper.cleanEmbeddedCassandra()
+ }
+
+
+ def executeCassandraQuery(queries: String*): Unit = {
+ if(null == session || session.isClosed){
+ session = CassandraConnector.getSession
+ }
+ for(query <- queries) {
+ session.execute(query)
+ }
+ }
+
+ def readFromCassandra(query: String) : ResultSet = {
+ if(null == session || session.isClosed){
+ session = CassandraConnector.getSession
+ }
+ session.execute(query)
+ }
+
+ def createRelationData(): Unit = {
+ graphDb.execute("UNWIND [{identifier:\"Num:C3:SC2\",code:\"Num:C3:SC2\",keywords:[\"Subconcept\",\"Class 3\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",subject:\"numeracy\",channel:\"in.ekstep\",description:\"Multiplication\",versionKey:\"1484389136575\",gradeLevel:[\"Grade 3\",\"Grade 4\"],IL_FUNC_OBJECT_TYPE:\"Concept\",name:\"Multiplication\",lastUpdatedOn:\"2016-06-15T17:15:45.951+0000\",IL_UNIQUE_ID:\"Num:C3:SC2\",status:\"Live\"}, {code:\"31d521da-61de-4220-9277-21ca7ce8335c\",previewUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",downloadUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",channel:\"in.ekstep\",language:[\"English\"],variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/ecar_files/do_11232724509261824014/untitled-content_1504790848197_do_11232724509261824014_2.0_spine.ecar\\\",\\\"size\\\":890.0}}\",mimeType:\"application/pdf\",streamingUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",idealScreenSize:\"normal\",createdOn:\"2017-09-07T13:24:20.720+0000\",contentDisposition:\"inline\",artifactUrl:\"https://ekstep-public-dev.s3-ap-south-1.amazonaws.com/assets/do_11232724509261824014/object-oriented-javascript.pdf\",contentEncoding:\"identity\",lastUpdatedOn:\"2017-09-07T13:25:53.595+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2017-09-07T13:27:28.417+0000\",contentType:\"Resource\",lastUpdatedBy:\"Ekstep\",audience:[\"Student\"],visibility:\"Default\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",consumerId:\"e84015d2-a541-4c07-a53f-e31d4553312b\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"Ekstep\",pkgVersion:2,versionKey:\"1504790848417\",license:\"Creative Commons Attribution (CC BY)\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_11232724509261824014/untitled-content_1504790847410_do_11232724509261824014_2.0.ecar\",size:4864851,lastPublishedOn:\"2017-09-07T13:27:27.410+0000\",createdBy:\"390\",compatibilityLevel:4,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Untitled Content\",publisher:\"EkStep\",IL_UNIQUE_ID:\"do_11232724509261824014\",status:\"Live\",resourceType:[\"Study material\"]}" +
+ ",{owner:\"in.ekstep\",code:\"NCF\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"NCF\",status:\"Live\",apoc_num:1}" +
+ ",{owner:\"in.ekstep\",code:\"K-12\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"K-12\",status:\"Live\",apoc_num:1}" +
+ ",{owner:\"in.ekstep\",code:\"tpd\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"tpd\",status:\"Live\",apoc_num:1}] as row CREATE (n:domain) SET n += row")
+ }
+}
diff --git a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala
new file mode 100644
index 000000000..af250f2cf
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala
@@ -0,0 +1,579 @@
+package org.sunbird.managers
+
+import java.util
+
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.sunbird.cache.impl.RedisCache
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.ClientException
+import org.sunbird.graph.OntologyEngineContext
+
+class TestHierarchy extends BaseSpec {
+
+ private val script_1 = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val script_2 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));"
+ private val script_3 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}');"
+ private val script_4 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414', '{\"status\":\"Live\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\"}]}}');"
+ private val script_5 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11323126798764441611181', '{\"identifier\":\"do_11323126798764441611181\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126798764441611181\",\"code\":\"U1\",\"keywords\":[],\"credentials\":{\"enabled\":\"No\"},\"channel\":\"sunbird\",\"description\":\"U1-For Content\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2021-03-07T19:24:58.991+0000\",\"objectType\":\"Collection\",\"primaryCategory\":\"Textbook Unit\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126865092608011182\",\"copyright\":\"Kerala State\",\"previewUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"keywords\":[\"By the Hands of the Nature\"],\"subject\":[\"Geography\"],\"channel\":\"0126202691023585280\",\"downloadUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",\"organisation\":[\"Kerala State\"],\"textbook_name\":[\"Contemporary India - I\"],\"showNotification\":true,\"language\":[\"English\"],\"source\":\"Kl 4\",\"mimeType\":\"application/pdf\",\"variants\":{\"spine\":{\"ecarUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087678819_do_11307457137049600011786_1.0_spine.ecar\",\"size\":36508.0}},\"objectType\":\"Content\",\"sourceURL\":\"https://diksha.gov.in/play/content/do_312783564254150656111171\",\"gradeLevel\":[\"Class 9\"],\"me_totalRatingsCount\":36,\"appIcon\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783564254150656111171/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.thumb.png\",\"primaryCategory\":\"Learning Resource\",\"level2Name\":[\"Physical Features of India\"],\"appId\":\"prod.diksha.portal\",\"contentEncoding\":\"identity\",\"artifactUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"me_totalPlaySessionCount\":{\"portal\":28},\"sYS_INTERNAL_LAST_UPDATED_ON\":\"2020-08-10T19:27:58.911+0000\",\"contentType\":\"Resource\",\"identifier\":\"do_11307457137049600011786\",\"audience\":[\"Student\"],\"me_totalTimeSpentInSec\":{\"portal\":2444},\"visibility\":\"Default\",\"author\":\"Kerala State\",\"consumerId\":\"89490534-126f-4f0b-82ac-3ff3e49f3468\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"lastPublishedBy\":\"ee2a003a-10a9-4152-8907-a905b9e1f943\",\"version\":2,\"pragma\":[\"external\"],\"license\":\"CC BY 4.0\",\"prevState\":\"Draft\",\"size\":1.000519E7,\"lastPublishedOn\":\"2020-08-10T19:27:57.797+0000\",\"name\":\"By the Hands of the Nature\",\"status\":\"Live\",\"code\":\"6633b233-bc5a-4936-a7a0-da37ebd33868\",\"prevStatus\":\"Processing\",\"origin\":\"do_312783564254150656111171\",\"streamingUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"medium\":[\"English\"],\"posterImage\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783565429334016112815/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.png\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-07-29T10:03:33.003+0000\",\"copyrightYear\":2019,\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-08-10T19:27:57.376+0000\",\"originData\":{\"identifier\":\"do_312783564254150656111171\",\"repository\":\"https://dock.sunbirded.org/do_312783564254150656111171\"},\"level1Concept\":[\"Physical Features of India\"],\"dialcodeRequired\":\"No\",\"owner\":\"Kerala SCERT\",\"lastStatusChangedOn\":\"2020-08-10T19:27:58.907+0000\",\"createdFor\":[\"0126202691023585280\"],\"creator\":\"SAJEEV THOMAS\",\"os\":[\"All\"],\"level1Name\":[\"Contemporary India - I\"],\"pkgVersion\":1.0,\"versionKey\":\"1597087677376\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"kl_k-12\",\"depth\":2,\"s3Key\":\"ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",\"me_averageRating\":3,\"createdBy\":\"f20a4bbf-df17-425b-8e43-bd3dd57bde83\",\"compatibilityLevel\":4,\"ownedBy\":\"0126202691023585280\",\"board\":\"CBSE\",\"resourceType\":\"Learn\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-03-07T19:24:58.990+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11323126865092608011182\",\"lastStatusChangedOn\":\"2021-03-07T19:24:58.991+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1615145098991\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U1\",\"status\":\"Draft\"},{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126798764441611181\",\"code\":\"U2\",\"keywords\":[],\"credentials\":{\"enabled\":\"No\"},\"channel\":\"sunbird\",\"description\":\"U2-For Other Objects\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2021-03-07T19:24:58.993+0000\",\"objectType\":\"Collection\",\"primaryCategory\":\"Textbook Unit\",\"children\":[{\"parent\":\"do_11323126865095065611184\",\"code\":\"finemanfine\",\"previewUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_html_1612875515166.html\",\"allowSkip\":\"Yes\",\"containsUserData\":\"No\",\"downloadUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875514981_do_113212597854404608111_5_SPINE.ecar\",\"description\":\"Updated QS Description\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.questionset\",\"showHints\":\"No\",\"variants\":{\"spine\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875514981_do_113212597854404608111_5_SPINE.ecar\",\"online\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875515115_do_113212597854404608111_5_ONLINE.ecar\"},\"createdOn\":\"2021-02-09T10:19:09.026+0000\",\"objectType\":\"QuestionSet\",\"pdfUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_pdf_1612875515932.pdf\",\"primaryCategory\":\"Practice Question Set\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-02-09T12:58:36.155+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"PracticeResource\",\"showSolutions\":\"Yes\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_113212597854404608111\",\"lastStatusChangedOn\":\"2021-02-09T12:58:36.155+0000\",\"requiresSubmit\":\"Yes\",\"visibility\":\"Default\",\"showTimer\":\"No\",\"summaryType\":\"Complete\",\"consumerId\":\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",\"childNodes\":[\"do_113212598840246272112\",\"do_113212599692050432114\",\"do_113212600505057280116\"],\"index\":1,\"setType\":\"materialised\",\"languageCode\":[\"en\"],\"version\":1,\"pkgVersion\":5,\"versionKey\":\"1612875494848\",\"showFeedback\":\"Yes\",\"license\":\"CC BY 4.0\",\"depth\":2,\"lastPublishedOn\":\"2021-02-09T12:58:34.976+0000\",\"compatibilityLevel\":5,\"name\":\"Test Question Set\",\"navigationMode\":\"linear\",\"shuffle\":true,\"status\":\"Live\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-03-07T19:24:58.993+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11323126865095065611184\",\"lastStatusChangedOn\":\"2021-03-07T19:24:58.993+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":2,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1615145098993\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U2\",\"status\":\"Draft\"}]}');"
+ private val script_6 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_26543193441064550414', '{\"identifier\":\"do_26543193441064550414\",\"children\":[{\"parent\":\"do_26543193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}');"
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+
+ override def beforeAll(): Unit = {
+ super.beforeAll()
+ graphDb.execute("UNWIND [{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120275141\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Parent\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Live\"},{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120960141\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Parent\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Draft\"},{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Live\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398314186_do_11283193441064550414_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/do_11283193441064550414_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"test prad course cert\",status:\"Live\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550414\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
+ executeCassandraQuery(script_1, script_2, script_3)
+ RedisCache.delete("hierarchy_do_11283193441064550414")
+ }
+
+
+ "addLeafNodesToHierarchy" should "addLeafNodesToHierarchy" in {
+ executeCassandraQuery(script_3)
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_112831862871203840114"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]]))
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215"))
+ assert(hierarchy.contains("do_112831862871203840114"))
+ })
+ }
+
+ "addLeafNodesToHierarchy with children having different objectType than supported one" should "throw ClientException" in {
+ executeCassandraQuery(script_3)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"SC-2200_3eac25ae-a0c9-4d7c-87be-954406824cb8\",channel:\"sunbird\",description:\"Test-Add/Remove Leaf Node\",language:[\"English\"],mimeType:\"video/mp4\",idealScreenSize:\"normal\",createdOn:\"2021-03-07T19:23:38.025+0000\",IL_FUNC_OBJECT_TYPE:\"Asset\",contentDisposition:\"inline\",additionalCategories:[\"Textbook\"],lastUpdatedOn:\"2021-03-07T19:24:59.023+0000\",contentEncoding:\"gzip\",contentType:\"Resource\",dialcodeRequired:\"No\",IL_UNIQUE_ID:\"do_asset_001\",lastStatusChangedOn:\"2021-03-07T19:23:38.025+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",mediaType:\"asset\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:\"1615145099023\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,compatibilityLevel:1,userConsent:\"Yes\",name:\"SC-2200-TextBook\",status:\"Draft\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_asset_001"))
+ request.put("mode","edit")
+ recoverToSucceededIf[ClientException](HierarchyManager.addLeafNodesToHierarchy(request))
+ }
+
+ ignore should "add the Question with draft status into hierarchy" in {
+ executeCassandraQuery(script_3)
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_113193462958120960141"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]]))
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215"))
+ assert(hierarchy.contains("do_113193462958120960141"))
+ })
+ }
+
+ ignore should "add Question as Leaf Node" in {
+ executeCassandraQuery(script_3)
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_113193462958120275141"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]]))
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215"))
+ assert(hierarchy.contains("do_113193462958120275141"))
+ })
+ }
+
+ "addLeafNodesToHierarchy for QuestionSet object" should "addLeafNodesToHierarchy" in {
+ executeCassandraQuery(script_3)
+ graphDb.execute("UNWIND [{copyright:\"Hello\",code:\"do_113193433773948928111\",allowSkip:\"Yes\",keywords:[\"135\",\"666667\"],containsUserData:\"No\",subject:[\"Hindi\"],description:\"Hello\",language:[\"English\"],medium:[\"Hindi\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2021-01-13T08:29:43.736+0000\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",gradeLevel:[\"Class 7\"],contentDisposition:\"inline\",additionalCategories:[\"Classroom Teaching Video\",\"Concept Map\",\"Textbook\",\"Curiosity Question Set\"],lastUpdatedOn:\"2021-02-08T12:20:33.201+0000\",contentEncoding:\"gzip\",showSolutions:\"Yes\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193433773948928111\",lastStatusChangedOn:\"2021-01-29T06:13:52.095+0000\",audience:[\"Teacher\"],requiresSubmit:\"Yes\",visibility:\"Default\",showTimer:\"Yes\",author:\"Hello\",summaryType:\"Complete\",consumerId:\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",childNodes:[\"do_113193462958120960141\",\"do_113193463656955904143\",\"do_113197944463515648120\",\"do_113209072358883328150\",\"do_113193462438895616139\"],setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1612786833201\",showFeedback:\"Yes\",license:\"CC BY 4.0\",prevState:\"Draft\",framework:\"ekstep_ncert_k-12\",depth:0,compatibilityLevel:4,name:\"u0926u0941u0903u0916 u0915u093E u0905u0927u093Fu0915u093Eu0930\",navigationMode:\"linear\",topic:[\"Leaves\",\"Water\"],shuffle:true,attributions:[\"Hello\"],board:\"CBSE\",status:\"Review\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_113193433773948928111"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]]))
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215"))
+ assert(hierarchy.contains("do_113193433773948928111"))
+ })
+ }
+
+ ignore should "remove Question object from hierarchy" in {
+ executeCassandraQuery(script_6)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"SC-2200_3eac25ae-a0c9-4d7c-87be-954406824cb8\",channel:\"sunbird\",description:\"Test-Add/Remove Leaf Node\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2021-03-07T19:23:38.025+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",contentDisposition:\"inline\",additionalCategories:[\"Textbook\"],lastUpdatedOn:\"2021-03-07T19:24:59.023+0000\",contentEncoding:\"gzip\",contentType:\"TextBook\",dialcodeRequired:\"No\",IL_UNIQUE_ID:\"do_26543193441064550414\",lastStatusChangedOn:\"2021-03-07T19:23:38.025+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",childNodes:[\"do_11307457137049600011786\",\"do_11323126865092608011182\",\"do_113212597854404608111\",\"do_11323126865095065611184\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:\"1615145099023\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,compatibilityLevel:1,userConsent:\"Yes\",name:\"SC-2200-TextBook\",status:\"Draft\"},{copyright:\"Hello\",code:\"do_113193433773948928111\",allowSkip:\"Yes\",keywords:[\"135\",\"666667\"],containsUserData:\"No\",subject:[\"Hindi\"],description:\"Hello\",language:[\"English\"],medium:[\"Hindi\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2021-01-13T08:29:43.736+0000\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",gradeLevel:[\"Class 7\"],contentDisposition:\"inline\",additionalCategories:[\"Classroom Teaching Video\",\"Concept Map\",\"Textbook\",\"Curiosity Question Set\"],lastUpdatedOn:\"2021-02-08T12:20:33.201+0000\",contentEncoding:\"gzip\",showSolutions:\"Yes\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193433773948928111\",lastStatusChangedOn:\"2021-01-29T06:13:52.095+0000\",audience:[\"Teacher\"],requiresSubmit:\"Yes\",visibility:\"Default\",showTimer:\"Yes\",author:\"Hello\",summaryType:\"Complete\",consumerId:\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",childNodes:[\"do_113193462958120960141\",\"do_113193463656955904143\",\"do_113197944463515648120\",\"do_113209072358883328150\",\"do_113193462438895616139\"],setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1612786833201\",showFeedback:\"Yes\",license:\"CC BY 4.0\",prevState:\"Draft\",framework:\"ekstep_ncert_k-12\",depth:0,compatibilityLevel:4,name:\"u0926u0941u0903u0916 u0915u093E u0905u0927u093Fu0915u093Eu0930\",navigationMode:\"linear\",topic:[\"Leaves\",\"Water\"],shuffle:true,attributions:[\"Hello\"],board:\"CBSE\",status:\"Review\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_26543193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_113193433773948928111"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ println("result ::::=="+response.getResult)
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_26543193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(hierarchy.contains("do_113193433773948928111"))
+ val removeFuture = HierarchyManager.removeLeafNodesFromHierarchy(request)
+ removeFuture.map(resp => {
+ assert(resp.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_26543193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!hierarchy.contains("do_113193433773948928111"))
+ })
+ }).flatMap(f => f)
+ }
+
+ ignore should "removeLeafNodesToHierarchy" in {
+ executeCassandraQuery(script_3)
+ graphDb.execute("UNWIND [{copyright:\"Hello\",code:\"do_113193433773948928111\",allowSkip:\"Yes\",keywords:[\"135\",\"666667\"],containsUserData:\"No\",subject:[\"Hindi\"],description:\"Hello\",language:[\"English\"],medium:[\"Hindi\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2021-01-13T08:29:43.736+0000\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",gradeLevel:[\"Class 7\"],contentDisposition:\"inline\",additionalCategories:[\"Classroom Teaching Video\",\"Concept Map\",\"Textbook\",\"Curiosity Question Set\"],lastUpdatedOn:\"2021-02-08T12:20:33.201+0000\",contentEncoding:\"gzip\",showSolutions:\"Yes\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193433773948928111\",lastStatusChangedOn:\"2021-01-29T06:13:52.095+0000\",audience:[\"Teacher\"],requiresSubmit:\"Yes\",visibility:\"Default\",showTimer:\"Yes\",author:\"Hello\",summaryType:\"Complete\",consumerId:\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",childNodes:[\"do_113193462958120960141\",\"do_113193463656955904143\",\"do_113197944463515648120\",\"do_113209072358883328150\",\"do_113193462438895616139\"],setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1612786833201\",showFeedback:\"Yes\",license:\"CC BY 4.0\",prevState:\"Draft\",framework:\"ekstep_ncert_k-12\",depth:0,compatibilityLevel:4,name:\"u0926u0941u0903u0916 u0915u093E u0905u0927u093Fu0915u093Eu0930\",navigationMode:\"linear\",topic:[\"Leaves\",\"Water\"],shuffle:true,attributions:[\"Hello\"],board:\"CBSE\",status:\"Review\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_113193462958120275141"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(hierarchy.contains("do_113193462958120275141"))
+ val removeFuture = HierarchyManager.removeLeafNodesFromHierarchy(request)
+ removeFuture.map(resp => {
+ assert(resp.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!hierarchy.contains("do_113193462958120275141"))
+ })
+ }).flatMap(f => f)
+ }
+
+ "removeLeafNodesToHierarchy" should "removeLeafNodesToHierarchy" in {
+ executeCassandraQuery(script_3)
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Collection")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11283193441064550414")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_112831862871203840114"))
+ request.put("mode","edit")
+ val future = HierarchyManager.addLeafNodesToHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(hierarchy.contains("do_112831862871203840114"))
+ val removeFuture = HierarchyManager.removeLeafNodesFromHierarchy(request)
+ removeFuture.map(resp => {
+ assert(resp.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(!hierarchy.contains("do_112831862871203840114"))
+ })
+ }).flatMap(f => f)
+ }
+
+ "addLeafNodesToHierarchy for shallowcopied" should "throw client exception " in {
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398314186_do_11298480837245337614_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11298480837245337614/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11298480837245337614/artifact/do_11298480837245337614_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Live\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11298480837245337614\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\",originData:\"{\\\"name\\\":\\\"Copy Collecction Testing For shallow Copy\\\",\\\"copyType\\\":\\\"shallow\\\",\\\"license\\\":\\\"CC BY 4.0\\\",\\\"organisation\\\":[\\\"test\\\"]}\"}] as row CREATE (n:domain) SET n += row");
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11298480837245337614")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_112831862871203840114"))
+ request.put("mode","edit")
+
+ recoverToSucceededIf[ClientException](HierarchyManager.addLeafNodesToHierarchy(request))
+
+ }
+
+ "removeLeafNodesToHierarchy for shallowcopied" should "throw client exception " in {
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398314186_do_11298480837245337614_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11298480837245337614/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11298480837245337614/artifact/do_11298480837245337614_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Live\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11298480837245337614/test-prad-course-cert_1566398313947_do_11298480837245337614_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11298480837245337614\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\",originData:\"{\\\"name\\\":\\\"Copy Collecction Testing For shallow Copy\\\",\\\"copyType\\\":\\\"shallow\\\",\\\"license\\\":\\\"CC BY 4.0\\\",\\\"organisation\\\":[\\\"test\\\"]}\"}] as row CREATE (n:domain) SET n += row");
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+
+ request.put("rootId", "do_11298480837245337614")
+ request.put("unitId", "do_11283193463014195215")
+ request.put("children", util.Arrays.asList("do_112831862871203840114"))
+ request.put("mode","edit")
+
+ recoverToSucceededIf[ClientException](HierarchyManager.removeLeafNodesFromHierarchy(request))
+
+ }
+
+// "getHierarchyWithInvalidIdentifier" should "Resourse_Not_Found" in {
+// val request = new Request()
+// request.setContext(new util.HashMap[String, AnyRef]() {
+// {
+// put("objectType", "Content")
+// put("graph_id", "domain")
+// put("version", "1.0")
+// put("schemaName", "collection")
+// }
+// })
+// request.put("rootId", "1234")
+// val future = HierarchyManager.getHierarchy(request)
+// future.map(response => {
+// assert(response.getResponseCode.code() == 404)
+// })
+// }
+
+ "getHierarchyForPublishedContent" should "getHierarchy" in {
+ val request = new Request()
+ executeCassandraQuery(script_4)
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ }
+ })
+ request.put("rootId", "do_11283193441064550414")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.getResult.get("content"))
+ assert(null != response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children"))
+ })
+ }
+
+ "getHierarchyWithEditMode" should "getHierarchy" in {
+ val request = new Request()
+ executeCassandraQuery(script_3)
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11283193441064550414")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.getResult.get("content"))
+ assert(null != response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children"))
+ })
+ }
+
+ "getHierarchyWithEditMode having QuestionSet object" should "return the hierarchy" in {
+ val request = new Request()
+ executeCassandraQuery(script_5)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"SC-2200_3eac25ae-a0c9-4d7c-87be-954406824cb8\",channel:\"sunbird\",description:\"Test-Add/Remove Leaf Node\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2021-03-07T19:23:38.025+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",contentDisposition:\"inline\",additionalCategories:[\"Textbook\"],lastUpdatedOn:\"2021-03-07T19:24:59.023+0000\",contentEncoding:\"gzip\",contentType:\"TextBook\",dialcodeRequired:\"No\",IL_UNIQUE_ID:\"do_11323126798764441611181\",lastStatusChangedOn:\"2021-03-07T19:23:38.025+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",childNodes:[\"do_11307457137049600011786\",\"do_11323126865092608011182\",\"do_113212597854404608111\",\"do_11323126865095065611184\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:\"1615145099023\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,compatibilityLevel:1,userConsent:\"Yes\",name:\"SC-2200-TextBook\",status:\"Draft\"}] as row CREATE (n:domain) SET n += row")
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"Kerala State\",previewUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",keywords:[\"By the Hands of the Nature\"],subject:[\"Geography\"],channel:\"0126202691023585280\",downloadUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",organisation:[\"Kerala State\"],textbook_name:[\"Contemporary India - I\"],showNotification:true,language:[\"English\"],source:\"Kl 4\",mimeType:\"application/pdf\",IL_FUNC_OBJECT_TYPE:\"Content\",sourceURL:\"https://diksha.gov.in/play/content/do_312783564254150656111171\",gradeLevel:[\"Class 9\"],me_totalRatingsCount:36,appIcon:\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783564254150656111171/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.thumb.png\",level2Name:[\"Physical Features of India\"],appId:\"prod.diksha.portal\",contentEncoding:\"identity\",artifactUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",sYS_INTERNAL_LAST_UPDATED_ON:\"2020-08-10T19:27:58.911+0000\",contentType:\"Resource\",IL_UNIQUE_ID:\"do_11307457137049600011786\",audience:[\"Student\"],visibility:\"Default\",author:\"Kerala State\",consumerId:\"89490534-126f-4f0b-82ac-3ff3e49f3468\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],lastPublishedBy:\"ee2a003a-10a9-4152-8907-a905b9e1f943\",version:2,pragma:[\"external\"],license:\"CC BY 4.0\",prevState:\"Draft\",size:10005190,lastPublishedOn:\"2020-08-10T19:27:57.797+0000\",name:\"By the Hands of the Nature\",status:\"Live\",code:\"6633b233-bc5a-4936-a7a0-da37ebd33868\",prevStatus:\"Processing\",origin:\"do_312783564254150656111171\",streamingUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",medium:[\"English\"],posterImage:\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783565429334016112815/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.png\",idealScreenSize:\"normal\",createdOn:\"2020-07-29T10:03:33.003+0000\",copyrightYear:2019,contentDisposition:\"inline\",lastUpdatedOn:\"2020-08-10T19:27:57.376+0000\",level1Concept:[\"Physical Features of India\"],dialcodeRequired:\"No\",owner:\"Kerala SCERT\",lastStatusChangedOn:\"2020-08-10T19:27:58.907+0000\",createdFor:[\"0126202691023585280\"],creator:\"SAJEEV THOMAS\",os:[\"All\"],level1Name:[\"Contemporary India - I\"],pkgVersion:1,versionKey:\"1597087677376\",idealScreenDensity:\"hdpi\",framework:\"kl_k-12\",s3Key:\"ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",me_averageRating:3,createdBy:\"f20a4bbf-df17-425b-8e43-bd3dd57bde83\",compatibilityLevel:4,ownedBy:\"0126202691023585280\",board:\"CBSE\",resourceType:\"Learn\"},{code:\"finemanfine\",previewUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_html_1612875515166.html\",allowSkip:\"Yes\",containsUserData:\"No\",downloadUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875514981_do_113212597854404608111_5_SPINE.ecar\",description:\"Updated QS Description\",language:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2021-02-09T10:19:09.026+0000\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",pdfUrl:\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_pdf_1612875515932.pdf\",contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-09T12:58:36.155+0000\",contentEncoding:\"gzip\",showSolutions:\"Yes\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113212597854404608111\",lastStatusChangedOn:\"2021-02-09T12:58:36.155+0000\",requiresSubmit:\"Yes\",visibility:\"Default\",showTimer:\"No\",summaryType:\"Complete\",consumerId:\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",childNodes:[\"do_113212598840246272112\",\"do_113212599692050432114\",\"do_113212600505057280116\"],setType:\"materialised\",languageCode:[\"en\"],version:1,pkgVersion:5,versionKey:\"1612875494848\",showFeedback:\"Yes\",license:\"CC BY 4.0\",depth:0,lastPublishedOn:\"2021-02-09T12:58:34.976+0000\",compatibilityLevel:5,name:\"Test Question Set\",navigationMode:\"linear\",shuffle:true,status:\"Live\"}] as row CREATE (n:domain) SET n += row")
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11323126798764441611181")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.getResult.get("content"))
+ println("hierarchy ::::: "+response.getResult.get("content"))
+ //assert(null != response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children"))
+ val children = response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
+ assert(null!=children && !children.isEmpty)
+ })
+ }
+
+ "getHierarchyForDraftAfterUpdateHierarchyWithoutMode" should "getHierarchy" in {
+ val request = new Request()
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Draft\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398314186_do_11283193441064550411_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/do_11283193441064550411_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Draft\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550411\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
+ executeCassandraQuery("INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550411', '{\"identifier\":\"do_11283193441064550411\",\"children\":[{\"parent\":\"do_11283193441064550411\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\"}]}')")
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+ request.put("rootId", "do_11283193441064550411")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 404)
+ })
+ }
+
+ "getHierarchyForDraftAfterUpdateHierarchyWithMode" should "getHierarchy" in {
+ val request = new Request()
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Draft\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398314186_do_11283193441064550411_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/do_11283193441064550411_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Draft\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550411\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
+ executeCassandraQuery("INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550411', '{\"identifier\":\"do_11283193441064550411\",\"children\":[{\"parent\":\"do_11283193441064550411\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\"}]}')")
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11283193441064550411")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.getResult.get("content"))
+ assert(null != response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children"))
+ })
+ }
+
+ "getHierarchyFromCache" should "getHierarchy" in {
+ val request = new Request()
+ executeCassandraQuery(script_4)
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+ request.put("rootId", "do_11283193441064550414")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != RedisCache.get("hierarchy_do_11283193441064550414"))
+ })
+ }
+
+ "getHierarchyBeforeUpdateHierarchyWithoutMode" should "getHierarchyWithoutChildren" in {
+ val request = new Request()
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Draft\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398314186_do_11283193441064550411_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550411/artifact/do_11283193441064550411_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Draft\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550411/test-prad-course-cert_1566398313947_do_11283193441064550411_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550411\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+ request.put("rootId", "do_11283193441064550411")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 404)
+ })
+ }
+
+ "getHierarchyBeforeUpdateHierarchyWithMode" should "getHierarchyWithoutChildren" in {
+ val request = new Request()
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Draft\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550412/test-prad-course-cert_1566398313947_do_11283193441064550412_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550412/test-prad-course-cert_1566398314186_do_11283193441064550412_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550412/test-prad-course-cert_1566398313947_do_11283193441064550412_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550412/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550412/artifact/do_11283193441064550412_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Draft\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550412/test-prad-course-cert_1566398313947_do_11283193441064550412_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550412\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11283193441064550412")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.get("content"))
+ assert(CollectionUtils.isEmpty(response.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[util.List[Map[String, AnyRef]]]))
+ })
+ }
+
+ "getHierarchy mode=edit" should "return latest leafNodes" in {
+ val query = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11300156035268608015', '{\"identifier\":\"do_11300156035268608015\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11300156035268608015\",\"code\":\"2cb4d698-dc19-4f0c-9990-96f49daff753\",\"channel\":\"in.ekstep\",\"description\":\"Test_TextBookUnit_desc_8330194200\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-04-17T11:53:04.855+0530\",\"objectType\":\"Content\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11300156075913216016\",\"code\":\"test-Resourcce\",\"channel\":\"in.ekstep\",\"language\":[\"English\"],\"mimeType\":\"application/pdf\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-04-17T11:51:30.230+0530\",\"objectType\":\"Content\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-04-17T11:51:30.230+0530\",\"contentEncoding\":\"identity\",\"contentType\":\"Resource\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11300155996401664014\",\"lastStatusChangedOn\":\"2020-04-17T11:51:30.230+0530\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Default\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1587104490230\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"NCF\",\"depth\":2,\"concepts\":[{\"identifier\":\"Num:C2:SC1\",\"name\":\"Counting\",\"description\":\"Counting\",\"objectType\":\"Concept\",\"relation\":\"associatedTo\",\"status\":\"Retired\"}],\"compatibilityLevel\":1,\"name\":\"test resource\",\"status\":\"Draft\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-04-17T11:53:04.855+0530\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11300156075913216016\",\"lastStatusChangedOn\":\"2020-04-17T11:53:04.855+0530\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"versionKey\":\"1587104584855\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"Test_TextBookUnit_name_7240493202\",\"status\":\"Draft\"}]}')"
+ executeCassandraQuery(query)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"txtbk\",channel:\"in.ekstep\",description:\"Text Book Test\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-04-17T11:52:15.303+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-04-17T11:53:05.434+0530\",contentType:\"Course\",dialcodeRequired:\"No\",identifier:\"do_11300156035268608015\",audience:[\"Student\"],lastStatusChangedOn:\"2020-04-17T11:52:15.303+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",childNodes:[\"do_11300156075913216016\",\"do_11300155996401664014\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1587104585434\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"TextBook\",IL_UNIQUE_ID:\"do_11300156035268608015\",status:\"Draft\"},{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",prevStatus:\"Live\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-04-17T11:51:30.230+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-04-17T13:38:24.720+0530\",contentType:\"Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-04-17T13:38:22.954+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1587110904720\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"updated\",IL_UNIQUE_ID:\"do_11300155996401664014\",status:\"Draft\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "in.ekstep")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11300156035268608015")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.get("content"))
+ val children = response.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[util.List[Map[String, AnyRef]]]
+ assert(CollectionUtils.isNotEmpty(children))
+ })
+ }
+
+ "getHierarchy mode=edit with bookmark" should "return latest leafNodes for bookmark" in {
+ val query = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_113054617607118848121','{\"identifier\":\"do_113054617607118848121\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_113054617607118848121\",\"code\":\"TestBookUnit-01\",\"keywords\":[],\"channel\":\"in.ekstep\",\"description\":\"U-1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-07-01T05:30:02.464+0000\",\"objectType\":\"Content\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_113054618848985088126\",\"code\":\"test.res.1\",\"previewUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_1/artifact/test_1592831799259.pdf\",\"prevStatus\":\"Live\",\"channel\":\"in.ekstep\",\"downloadUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_test_content_1/g-test-pdf-1_1592831801712_do_test_content_1_1.0.ecar\",\"language\":[\"English\"],\"streamingUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_1/artifact/test_1592831799259.pdf\",\"mimeType\":\"application/pdf\",\"variants\":{\"spine\":{\"ecarUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_test_content_1/g-test-pdf-1_1592831801948_do_test_content_1_1.0_spine.ecar\",\"size\":849}},\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-06-22T13:16:39.135+0000\",\"objectType\":\"ContentImage\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-06-22T13:16:40.506+0000\",\"contentEncoding\":\"identity\",\"artifactUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_1/artifact/test_1592831799259.pdf\",\"sYS_INTERNAL_LAST_UPDATED_ON\":\"2020-06-22T13:16:42.230+0000\",\"contentType\":\"Resource\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_test_content_1\",\"lastStatusChangedOn\":\"2020-06-23T12:07:01.047+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Default\",\"cloudStorageKey\":\"content/do_test_content_1/artifact/test_1592831799259.pdf\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"lastPublishedBy\":\"EkStep\",\"version\":2,\"pragma\":[\"external\"],\"pkgVersion\":1,\"versionKey\":\"1592914021107\",\"license\":\"CC BY 4.0\",\"prevState\":\"Draft\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"NCFCOPY\",\"depth\":2,\"s3Key\":\"ecar_files/do_test_content_1/g-test-pdf-1_1592831801712_do_test_content_1_1.0.ecar\",\"size\":1946,\"lastPublishedOn\":\"2020-06-22T13:16:40.672+0000\",\"compatibilityLevel\":4,\"name\":\"G-TEST-PDF-1\",\"status\":\"Live\",\"description\":\"updated for do_test_content_1\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-07-01T05:30:02.463+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_113054618848985088126\",\"lastStatusChangedOn\":\"2020-07-01T05:30:02.464+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"versionKey\":\"1593581402464\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U-1\",\"status\":\"Live\"},{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_113054617607118848121\",\"code\":\"TestBookUnit-02\",\"keywords\":[],\"channel\":\"in.ekstep\",\"description\":\"U-2\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-07-01T05:30:02.458+0000\",\"objectType\":\"Content\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_113054618848935936124\",\"code\":\"test.res.2\",\"previewUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_2/artifact/test_1592831800654.pdf\",\"prevStatus\":\"Live\",\"channel\":\"in.ekstep\",\"downloadUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_test_content_2/g-test-pdf-2_1592831806405_do_test_content_2_1.0.ecar\",\"language\":[\"English\"],\"streamingUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_2/artifact/test_1592831800654.pdf\",\"mimeType\":\"application/pdf\",\"variants\":{\"spine\":{\"ecarUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_test_content_2/g-test-pdf-2_1592831806559_do_test_content_2_1.0_spine.ecar\",\"size\":847}},\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-06-22T13:16:40.626+0000\",\"objectType\":\"ContentImage\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-06-22T13:16:42.293+0000\",\"contentEncoding\":\"identity\",\"artifactUrl\":\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_test_content_2/artifact/test_1592831800654.pdf\",\"sYS_INTERNAL_LAST_UPDATED_ON\":\"2020-06-22T13:16:46.836+0000\",\"contentType\":\"Resource\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_test_content_2\",\"lastStatusChangedOn\":\"2020-06-23T12:07:01.269+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Default\",\"cloudStorageKey\":\"content/do_test_content_2/artifact/test_1592831800654.pdf\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"lastPublishedBy\":\"EkStep\",\"version\":2,\"pragma\":[\"external\"],\"pkgVersion\":1,\"versionKey\":\"1592914021297\",\"license\":\"CC BY 4.0\",\"prevState\":\"Draft\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"NCFCOPY\",\"depth\":2,\"s3Key\":\"ecar_files/do_test_content_2/g-test-pdf-2_1592831806405_do_test_content_2_1.0.ecar\",\"size\":1941,\"lastPublishedOn\":\"2020-06-22T13:16:42.447+0000\",\"compatibilityLevel\":4,\"name\":\"G-TEST-PDF-2\",\"status\":\"Live\",\"description\":\"updated for do_test_content_2\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-07-01T05:30:02.457+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_113054618848935936124\",\"lastStatusChangedOn\":\"2020-07-01T05:30:02.458+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":2,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"versionKey\":\"1593581402458\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U-2\",\"status\":\"Live\"}]}')"
+ executeCassandraQuery(query)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"test.book.1\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-07-01T05:27:30.873+0000\",objectType:\"Content\",contentDisposition:\"inline\",lastUpdatedOn:\"2020-07-01T05:30:02.963+0000\",contentEncoding:\"gzip\",contentType:\"TextBook\",dialcodeRequired:\"No\",identifier:\"do_113054617607118848121\",lastStatusChangedOn:\"2020-07-01T05:27:30.873+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",childNodes:[\"do_test_content_1\",\"do_113054618848985088126\",\"do_test_content_2\",\"do_113054618848935936124\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:1593581402963,license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",framework:\"NCF\",depth:0,compatibilityLevel:1,name:\"Test-Get Hierarchy\",status:\"Draft\",IL_UNIQUE_ID:\"do_113054617607118848121\",IL_FUNC_OBJECT_TYPE:\"Content\",IL_SYS_NODE_TYPE:\"DATA_NODE\"},\n{ownershipType:[\"createdBy\"],code:\"test.res.1\",prevStatus:\"Live\",channel:\"in.ekstep\",description:\"updated for do_test_content_1\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-06-22T13:16:39.135+0000\",objectType:\"ContentImage\",contentDisposition:\"inline\",lastUpdatedOn:\"2020-06-22T13:16:40.506+0000\",contentEncoding:\"identity\",sYS_INTERNAL_LAST_UPDATED_ON:\"2020-06-22T13:16:42.230+0000\",contentType:\"Resource\",dialcodeRequired:\"No\",identifier:\"do_test_content_1\",lastStatusChangedOn:\"2020-06-23T12:07:01.047+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"EkStep\",languageCode:[\"en\"],version:2,pragma:[\"external\"],pkgVersion:1,versionKey:1592914021107,license:\"CC BY 4.0\",prevState:\"Draft\",idealScreenDensity:\"hdpi\",framework:\"NCFCOPY\",size:1946,lastPublishedOn:\"2020-06-22T13:16:40.672+0000\",compatibilityLevel:4,name:\"G-TEST-PDF-1\",status:\"Draft\",IL_UNIQUE_ID:\"do_test_content_1\",IL_FUNC_OBJECT_TYPE:\"Content\",IL_SYS_NODE_TYPE:\"DATA_NODE\"},\n{ownershipType:[\"createdBy\"],code:\"test.res.2\",prevStatus:\"Live\",channel:\"in.ekstep\",description:\"updated for do_test_content_2\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-06-22T13:16:40.626+0000\",objectType:\"ContentImage\",contentDisposition:\"inline\",lastUpdatedOn:\"2020-06-22T13:16:42.293+0000\",contentEncoding:\"identity\",sYS_INTERNAL_LAST_UPDATED_ON:\"2020-06-22T13:16:46.836+0000\",contentType:\"Resource\",dialcodeRequired:\"No\",identifier:\"do_test_content_2\",lastStatusChangedOn:\"2020-06-23T12:07:01.269+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"EkStep\",languageCode:[\"en\"],version:2,pragma:[\"external\"],pkgVersion:1,versionKey:1592914021297,license:\"CC BY 4.0\",prevState:\"Draft\",idealScreenDensity:\"hdpi\",framework:\"NCFCOPY\",s3Key:\"ecar_files/do_test_content_2/g-test-pdf-2_1592831806405_do_test_content_2_1.0.ecar\",size:1941,lastPublishedOn:\"2020-06-22T13:16:42.447+0000\",compatibilityLevel:4,name:\"G-TEST-PDF-2\",status:\"Draft\",IL_UNIQUE_ID:\"do_test_content_2\",IL_FUNC_OBJECT_TYPE:\"Content\",IL_SYS_NODE_TYPE:\"DATA_NODE\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "in.ekstep")
+ }
+ })
+ request.put("mode","edit")
+ request.put("bookmarkId","do_113054618848985088126")
+ request.put("rootId", "do_113054617607118848121")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.get("content"))
+ val children = response.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[util.List[Map[String, AnyRef]]]
+ assert(CollectionUtils.isNotEmpty(children))
+ assert(children.size()==1)
+ val childrenMap = children.get(0).asInstanceOf[util.Map[String, AnyRef]]
+ assert(StringUtils.equalsIgnoreCase(childrenMap.get("status").asInstanceOf[String],"Draft"))
+ assert(StringUtils.equalsIgnoreCase(childrenMap.get("identifier").asInstanceOf[String],"do_test_content_1"))
+ })
+ }
+
+
+ "getHierarchy mode=edit" should "return removing retired or deleted leafNodes" in {
+ val query = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11300156035268608015', '{\"identifier\":\"do_11300156035268608015\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11300156035268608015\",\"code\":\"2cb4d698-dc19-4f0c-9990-96f49daff753\",\"channel\":\"in.ekstep\",\"description\":\"Test_TextBookUnit_desc_8330194200\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-04-17T11:53:04.855+0530\",\"objectType\":\"Content\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11300156075913216016\",\"code\":\"test-Resourcce\",\"channel\":\"in.ekstep\",\"language\":[\"English\"],\"mimeType\":\"application/pdf\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-04-17T11:51:30.230+0530\",\"objectType\":\"Content\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-04-17T11:51:30.230+0530\",\"contentEncoding\":\"identity\",\"contentType\":\"Resource\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_1130015599640166\",\"lastStatusChangedOn\":\"2020-04-17T11:51:30.230+0530\",\"audience\":[\"Learner\"],\"os\":[\"All\"],\"visibility\":\"Default\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1587104490230\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"NCF\",\"depth\":2,\"concepts\":[{\"identifier\":\"Num:C2:SC1\",\"name\":\"Counting\",\"description\":\"Counting\",\"objectType\":\"Concept\",\"relation\":\"associatedTo\",\"status\":\"Retired\"}],\"compatibilityLevel\":1,\"name\":\"test resource\",\"status\":\"Draft\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-04-17T11:53:04.855+0530\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11300156075913216016\",\"lastStatusChangedOn\":\"2020-04-17T11:53:04.855+0530\",\"audience\":[\"Learner\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"versionKey\":\"1587104584855\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"Test_TextBookUnit_name_7240493202\",\"status\":\"Draft\"}]}')"
+ executeCassandraQuery(query)
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"txtbk\",channel:\"in.ekstep\",description:\"Text Book Test\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-04-17T11:52:15.303+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-04-17T11:53:05.434+0530\",contentType:\"Course\",dialcodeRequired:\"No\",identifier:\"do_11300156035268608015\",audience:[\"Learner\"],lastStatusChangedOn:\"2020-04-17T11:52:15.303+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",childNodes:[\"do_11300156075913216016\",\"do_11300155996401664014\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1587104585434\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"TextBook\",IL_UNIQUE_ID:\"do_11300156035268608015\",status:\"Draft\"},{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",prevStatus:\"Live\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-04-17T11:51:30.230+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-04-17T13:38:24.720+0530\",contentType:\"Resource\",dialcodeRequired:\"No\",audience:[\"Learner\"],lastStatusChangedOn:\"2020-04-17T13:38:22.954+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1587110904720\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"updated\",IL_UNIQUE_ID:\"do_11300155996401664014\",status:\"Draft\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ request.setContext(new util.HashMap[String, AnyRef]() {
+ {
+ put("objectType", "Content")
+ put("graph_id", "domain")
+ put("version", "1.0")
+ put("schemaName", "collection")
+ put("channel", "in.ekstep")
+ }
+ })
+ request.put("mode","edit")
+ request.put("rootId", "do_11300156035268608015")
+ val future = HierarchyManager.getHierarchy(request)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.get("content"))
+ val children = response.get("content").asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[util.List[Map[String, AnyRef]]]
+ assert(CollectionUtils.isNotEmpty(children))
+ assert(CollectionUtils.isEmpty(children.get(0).asInstanceOf[util.Map[String, AnyRef]].get("children").asInstanceOf[util.List[Map[String, AnyRef]]]))
+ })
+ }
+}
diff --git a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala
new file mode 100644
index 000000000..64929da34
--- /dev/null
+++ b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala
@@ -0,0 +1,509 @@
+package org.sunbird.managers
+
+import java.util
+
+import org.apache.commons.lang3.BooleanUtils
+import org.parboiled.common.StringUtils
+import org.sunbird.common.JsonUtils
+import org.sunbird.common.dto.Request
+import org.sunbird.common.exception.{ClientException, ResourceNotFoundException}
+import org.sunbird.graph.OntologyEngineContext
+import org.sunbird.utils.HierarchyConstants
+
+class TestUpdateHierarchy extends BaseSpec {
+
+ private val KEYSPACE_CREATE_SCRIPT = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val TABLE_CREATE_SCRIPT = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));"
+ private val HIERARCHY_TO_MIGRATE_SCRIPT = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414.img', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"primaryCategory\":\"Learning Resource\",\"status\":\"Draft\"}]}');"
+
+ private val CATEGORY_STORE_KEYSPACE = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
+ private val CATEGORY_DEF_DATA_TABLE = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text PRIMARY KEY, forms map, objectmetadata map);"
+ private val CATEGORY_DEF_INPUT = List("INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:digital-textbook_collection_all')",
+ "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:textbook-unit_collection_all')",
+ "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:course-unit_collection_all')",
+ "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:content-playlist_collection_all')",
+ "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:asset_asset_all')",
+ "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:explanation-content_content_all')")
+
+// private val EXISTING_HIERARCHY_do_112949210157768704111 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_112949210157768704111', '{\"identifier\":\"do_112949210157768704111\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_112949210157768704111\",\"code\":\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\",\"channel\":\"in.ekstep\",\"description\":\"Test_CourseUnit_desc_1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-02-04T10:53:23.491+0530\",\"objectType\":\"Content\",\"children\":[{\"ownershipType\":[\"createdFor\"],\"parent\":\"do_11294986283819827217\",\"previewUrl\":\"https://youtu.be/v7YZhQ86Adw\",\"keywords\":[\"10th\",\"Science\",\"Jnana Prabodhini\",\"Maharashtra Board\",\"#gyanqr\"],\"subject\":[\"Science\"],\"channel\":\"01261732844414566415\",\"downloadUrl\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/ecar_files/do_312776559940476928110909/vnsptiinmdhye-laingik-prjnn_1560157123850_do_312776559940476928110909_1.0.ecar\",\"organisation\":[\"Jnana Prabodhini\"],\"language\":[\"English\"],\"mimeType\":\"video/x-youtube\",\"variants\":{\"spine\":{\"ecarUrl\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/ecar_files/do_312776559940476928110909/vnsptiinmdhye-laingik-prjnn_1560157123878_do_312776559940476928110909_1.0_spine.ecar\",\"size\":51205.0}},\"objectType\":\"Content\",\"gradeLevel\":[\"Class 10\"],\"appIcon\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312776559940476928110909/artifact/10th_mar_2_1547715340679.thumb.png\",\"appId\":\"prod.diksha.portal\",\"contentEncoding\":\"identity\",\"artifactUrl\":\"https://youtu.be/v7YZhQ86Adw\",\"lockKey\":\"772b40b3-4de0-44c3-8474-0fe8f8ec2d91\",\"sYS_INTERNAL_LAST_UPDATED_ON\":\"2019-07-31T01:57:11.210+0000\",\"contentType\":\"Resource\",\"lastUpdatedBy\":\"bf4df886-bb42-4f91-9f33-c88da1653535\",\"identifier\":\"do_312776559940476928110909\",\"audience\":[\"Student\"],\"visibility\":\"Default\",\"consumerId\":\"89490534-126f-4f0b-82ac-3ff3e49f3468\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"lastPublishedBy\":\"7a3358d5-e290-49a4-b7ea-3e3d47a2af30\",\"languageCode\":[\"en\"],\"version\":1,\"pragma\":[\"external\"],\"license\":\"Creative Commons Attribution (CC BY)\",\"prevState\":\"Review\",\"size\":51204.0,\"lastPublishedOn\":\"2019-06-10T08:58:43.846+0000\",\"name\":\"वनस्पतींमध्ये लैंगिक प्रजनन\",\"attributions\":[\"Jnana Prabodhini\"],\"status\":\"Live\",\"code\":\"99a9c6e4-ec56-40ab-9a8c-b66e3a551273\",\"creators\":\"Jnana Prabodhini\",\"description\":\"सजीवांतील जीवनप्रक्रिया भाग - २\",\"streamingUrl\":\"https://youtu.be/v7YZhQ86Adw\",\"medium\":[\"Marathi\"],\"posterImage\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_31267888406854041612953/artifact/10th_mar_2_1547715340679.png\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2018-09-06T06:35:10.427+0000\",\"contentDisposition\":\"online\",\"lastUpdatedOn\":\"6019-06-10T08:35:10.582+0000\",\"dialcodeRequired\":\"No\",\"owner\":\"Jnana Prabodhini\",\"lastStatusChangedOn\":\"2019-06-10T08:58:43.925+0000\",\"createdFor\":[\"01261732844414566415\"],\"creator\":\"Pallavi Paradkar\",\"os\":[\"All\"],\"pkgVersion\":1.0,\"versionKey\":\"1560157123651\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"NCF\",\"depth\":2,\"s3Key\":\"ecar_files/do_312776559940476928110909/vnsptiinmdhye-laingik-prjnn_1560157123850_do_312776559940476928110909_1.0.ecar\",\"me_averageRating\":3.0,\"lastSubmittedOn\":\"2019-06-04T09:02:44.995+0000\",\"createdBy\":\"bf4df886-bb42-4f91-9f33-c88da1653535\",\"compatibilityLevel\":4,\"ownedBy\":\"01261732844414566415\",\"board\":\"State (Maharashtra)\",\"resourceType\":\"Learn\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-02-04T10:53:23.490+0530\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11294986283819827217\",\"lastStatusChangedOn\":\"2020-02-04T10:53:23.492+0530\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"versionKey\":\"1580793803491\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"Test_CourseUnit_1\",\"status\":\"Draft\"}]}');"
+
+ implicit val oec: OntologyEngineContext = new OntologyEngineContext
+
+ override def beforeAll(): Unit = {
+ super.beforeAll()
+ graphDb.execute("UNWIND [" +
+ "{identifier:\"obj-cat:learning-resource_content_0123221617357783046602\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:learning-resource\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:learning-resource_content_0123221617357783046602\"}," +
+ "{identifier:\"obj-cat:learning-resource_content_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:learning-resource\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:learning-resource_content_all\"}," +
+ "{ownershipType:[\"createdBy\"],code:\"citrusCode\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-01-29T17:45:55.620+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-01-29T17:46:35.471+0530\",contentType:\"TextBook\",primaryCategory:\"Digital Textbook\",dialcodeRequired:\"No\",identifier:\"do_11294581887465881611\",audience:[\"Student\"],lastStatusChangedOn:\"2020-01-29T17:45:55.620+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",nodeType:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1580300195471\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"Update Hierarchy Test_03\",IL_UNIQUE_ID:\"do_11294581887465881611\",status:\"Draft\"}," +
+ "{ownershipType:[\"createdBy\"],code:\"citrusCode\",channel:\"in.ekstep\",description:\"New text book description_01\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-02-03T12:45:30.605+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-02-03T12:46:01.439+0530\",contentType:\"TextBook\",primaryCategory:\"Digital Textbook\",dialcodeRequired:\"No\",identifier:\"do_112949210157768704111\",audience:[\"Student\"],lastStatusChangedOn:\"2020-02-03T12:45:30.605+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",nodeType:\"DATA_NODE\",childNodes:[\"do_112949210410000384114\"],mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1580714161439\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"Update Hierarchy Test_01\",IL_UNIQUE_ID:\"do_112949210157768704111\",status:\"Draft\"}," +
+ "{ownershipType:[\"createdBy\"],previewUrl:\"https://www.youtube.com/watch?v=JEjUtGkUqus\",downloadUrl:\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/ecar_files/do_31250856200414822416938/mh_chapter-1_science-part-2_grade-10_2_1539192600492_do_31250856200414822416938_2.0.ecar\",channel:\"0123221617357783046602\",showNotification:true,language:[\"English\"],variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/ecar_files/do_31250856200414822416938/mh_chapter-1_science-part-2_grade-10_2_1539192600579_do_31250856200414822416938_2.0_spine.ecar\\\",\\\"size\\\":29518}}\",mimeType:\"video/x-youtube\",appIcon:\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_31250856200414822416938/artifact/logo_4221_1513150964_1513150964262.thumb.jpg\",appId:\"prod.diksha.app\",artifactUrl:\"https://www.youtube.com/watch?v=JEjUtGkUqus\",contentEncoding:\"identity\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",lastUpdatedBy:\"ekstep\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"89490534-126f-4f0b-82ac-3ff3e49f3468\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"ekstep\",version:1,pragma:[\"external\"],license:\"CC BY-SA 4.0\",prevState:\"Live\",size:29519,lastPublishedOn:\"2018-10-10T17:30:00.491+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"MH_Chapter 1_Science Part 2_Grade 10_2\",attributions:[\"MSCERT\"],status:\"Live\",code:\"b2099ea5-0070-4930-ae13-b08aa83e5853\",description:\"अानुवांशिकता और उत्क्रांती\",streamingUrl:\"https://www.youtube.com/watch?v=JEjUtGkUqus\",posterImage:\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_31239573269507276815296/artifact/logo_4221_1513150964_1513150964262.jpg\",idealScreenSize:\"normal\",createdOn:\"0021-02-04T08:05:49.480+0000\",contentDisposition:\"online\",lastUpdatedOn:\"25182518-10-10T17:29:59.456+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-07-31T01:57:26.148+0000\",dialcodeRequired:\"No\",lastStatusChangedOn:\"2019-06-17T05:41:05.507+0000\",createdFor:[\"0123221617357783046602\"],creator:\"Alaka Potdar\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",nodeType:\"DATA_NODE\",pkgVersion:2,versionKey:\"1539192599456\",idealScreenDensity:\"hdpi\",framework:\"NCF\",s3Key:\"ecar_files/do_31250856200414822416938/mh_chapter-1_science-part-2_grade-10_2_1539192600492_do_31250856200414822416938_2.0.ecar\",me_averageRating:3,lastSubmittedOn:\"2018-05-21T17:37:16.466+0000\",createdBy:\"c5d09e49-6f1d-474b-b6cc-2e590ae15ef8\",compatibilityLevel:4,IL_UNIQUE_ID:\"do_31250856200414822416938\",resourceType:\"Learn\"}," +
+ "{copyright:\"\",code:\"org.ekstep.asset.Pant.1773380908\",sources:\"\",channel:\"in.ekstep\",downloadUrl:\"https://ekstep-public-prod.s3-ap-south-1.amazonaws.com/content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.png\",language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"high\\\":\\\"https://ekstep-public-prod.s3-ap-south-1.amazonaws.com/content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.png\\\",\\\"low\\\":\\\"https://ekstep-public-prod.s3-ap-south-1.amazonaws.com/content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.low.png\\\",\\\"medium\\\":\\\"https://ekstep-public-prod.s3-ap-south-1.amazonaws.com/content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.medium.png\\\"}\",idealScreenSize:\"normal\",createdOn:\"2017-01-02T07:02:31.021+0000\",contentDisposition:\"inline\",artifactUrl:\"https://ekstep-public-prod.s3-ap-south-1.amazonaws.com/content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.png\",contentEncoding:\"identity\",lastUpdatedOn:\"2017-03-10T20:48:09.283+0000\",contentType:\"Resource\",owner:\"\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",nodeType:\"DATA_NODE\",portalOwner:\"658\",mediaType:\"image\",osId:\"org.ekstep.quiz.app\",ageGroup:[\"5-6\"],versionKey:\"1489178889283\",license:\"CC BY-SA 4.0\",idealScreenDensity:\"hdpi\",framework:\"NCF\",s3Key:\"content/do_111112224444/artifact/clothes-1294974_960_720_658_1483340550_1483340551056.png\",size:167939,createdBy:\"658\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Pant\",publisher:\"\",IL_UNIQUE_ID:\"do_111112224444\",status:\"Live\"}," +
+ "{ownershipType:[\"createdBy\"],code:\"citrusCode\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-01-29T17:45:55.620+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-01-29T17:46:35.471+0530\",contentType:\"TextBook\",primaryCategory:\"Digital Textbook\",dialcodeRequired:\"No\",identifier:\"do_test_book_1\",audience:[\"Student\"],lastStatusChangedOn:\"2020-01-29T17:45:55.620+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",nodeType:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1580300195471\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"Update Hierarchy Test For Origin Data\",IL_UNIQUE_ID:\"do_test_book_1\",status:\"Draft\"}," +
+ "{identifier:\"obj-cat:learning-resource_content_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:learning-resource\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:learning-resource_content_all\"}," +
+ "{identifier:\"obj-cat:learning-resource_content_b00bc992ef25f1a9a8d63291e20efc8d\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:learning-resource\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:learning-resource_content_b00bc992ef25f1a9a8d63291e20efc8d\"}," +
+ "{identifier:\"obj-cat:digital-textbook_collection_all\",name:\"Digital Textbook\",description:\"Learning resource\",categoryId:\"obj-cat:digital-textbook\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:digital-textbook_collection_all\"}," +
+ "{identifier:\"obj-cat:textbook-unit_collection_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:textbook-unit\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:textbook-unit_collection_all\"}," +
+ "{identifier:\"obj-cat:course_collection_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:course\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:course_collection_all\"}," +
+ "{identifier:\"obj-cat:course-unit_collection_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:course_unit\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:course-unit_collection_all\"}," +
+ "{identifier:\"obj-cat:asset_asset_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:asset\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:asset_asset_all\"}," +
+ "{identifier:\"obj-cat:content-playlist_collection_all\",name:\"Learning Resource\",description:\"Learning resource\",categoryId:\"obj-cat:content-playlist\",targetObjectType:\"Content\",status:\"Live\",objectMetadata:\"{\\\"config\\\":{},\\\"schema\\\":{\\\"properties\\\":{\\\"trackable\\\":{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"enabled\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"},\\\"autoBatch\\\":{\\\"type\\\":\\\"string\\\",\\\"enum\\\":[\\\"Yes\\\",\\\"No\\\"],\\\"default\\\":\\\"Yes\\\"}},\\\"additionalProperties\\\":false}}}}\",IL_SYS_NODE_TYPE:\"DATA_NODE\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",IL_UNIQUE_ID:\"obj-cat:content-playlist_collection_all\"}" +
+ "] as row CREATE (n:domain) SET n += row")
+ executeCassandraQuery(KEYSPACE_CREATE_SCRIPT, TABLE_CREATE_SCRIPT, CATEGORY_STORE_KEYSPACE, CATEGORY_DEF_DATA_TABLE)
+ executeCassandraQuery(CATEGORY_DEF_INPUT:_*)
+ insert20NodesAndOneCourse()
+ }
+
+ override def beforeEach(): Unit = {
+ executeCassandraQuery( HIERARCHY_TO_MIGRATE_SCRIPT)
+ }
+
+ def getContext(): java.util.Map[String, AnyRef] = {
+ new util.HashMap[String, AnyRef](){{
+ put(HierarchyConstants.GRAPH_ID, HierarchyConstants.TAXONOMY_ID)
+ put(HierarchyConstants.VERSION , HierarchyConstants.SCHEMA_VERSION)
+ put(HierarchyConstants.OBJECT_TYPE , HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ put(HierarchyConstants.SCHEMA_NAME, "content")
+ put(HierarchyConstants.CHANNEL, "b00bc992ef25f1a9a8d63291e20efc8d")
+ }}
+ }
+
+ "deleteHierarchy" should "Delete the hierarchy data from cassandra from identifier with .img extension" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ context.put(HierarchyConstants.ROOT_ID, "do_11283193441064550414")
+ request.setContext(context)
+ val oldHierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(oldHierarchy))
+ UpdateHierarchyManager.deleteHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(BooleanUtils.isFalse(readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'").iterator().hasNext))
+ })
+ }
+
+ "deleteHierarchy with image id" should "Delete the hierarchy data from cassandra from identifier with .img extension" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ context.put(HierarchyConstants.ROOT_ID, "do_11283193441064550414.img")
+ request.setContext(context)
+ val oldHierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(oldHierarchy))
+ UpdateHierarchyManager.deleteHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(BooleanUtils.isFalse(readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'").iterator().hasNext))
+ })
+ }
+
+ "deleteHierarchy with invalid id" should "Delete the hierarchy data from cassandra from identifier with .img extension" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ context.put(HierarchyConstants.ROOT_ID, "123")
+ request.setContext(context)
+ UpdateHierarchyManager.deleteHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(hierarchy))
+ })
+ }
+
+// ResourceId = "do_31250856200414822416938" and TextBook id ="do_112945818874658816"
+ "updateHierarchy with One New Unit and One Live Resource" should "update text book node, create unit and store the hierarchy in cassandra" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.setObjectType(HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_1())
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_1())
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11294581887465881611'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(hierarchy))
+ })
+ }
+
+ "updateHierarchy with One New Unit and Quesstion Object" should "update text book node, create unit and store the hierarchy in cassandra" in {
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"SC-2200_3eac25ae-a0c9-4d7c-87be-954406824cb8\",channel:\"sunbird\",description:\"Test-Add/Remove Leaf Node\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2021-03-07T19:23:38.025+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",contentDisposition:\"inline\",additionalCategories:[\"Textbook\"],lastUpdatedOn:\"2021-03-07T19:24:59.023+0000\",contentEncoding:\"gzip\",contentType:\"TextBook\",dialcodeRequired:\"No\",IL_UNIQUE_ID:\"do_111111111222222222\",lastStatusChangedOn:\"2021-03-07T19:23:38.025+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:\"1615145099023\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,compatibilityLevel:1,userConsent:\"Yes\",name:\"SC-2200-TextBook\",status:\"Draft\"},{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120275141\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Default\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Live\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.setObjectType(HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_3())
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_3("do_111111111222222222", "do_113193462958120275141"))
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_111111111222222222'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(hierarchy))
+ })
+ }
+
+ "updateHierarchy with One New Unit and QuesstionSet Object" should "update text book node, create unit and store the hierarchy in cassandra" in {
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"SC-2200_3eac25ae-a0c9-4d7c-87be-954406824cb8\",channel:\"sunbird\",description:\"Test-Add/Remove Leaf Node\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2021-03-07T19:23:38.025+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",contentDisposition:\"inline\",additionalCategories:[\"Textbook\"],lastUpdatedOn:\"2021-03-07T19:24:59.023+0000\",contentEncoding:\"gzip\",contentType:\"TextBook\",dialcodeRequired:\"No\",IL_UNIQUE_ID:\"do_1111111112222222222223\",lastStatusChangedOn:\"2021-03-07T19:23:38.025+0000\",audience:[\"Student\"],os:[\"All\"],visibility:\"Default\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",languageCode:[\"en\"],version:2,versionKey:\"1615145099023\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,compatibilityLevel:1,userConsent:\"Yes\",name:\"SC-2200-TextBook\",status:\"Draft\"},{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120277239\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Default\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Live\"}] as row CREATE (n:domain) SET n += row")
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.setObjectType(HierarchyConstants.COLLECTION_OBJECT_TYPE)
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_3())
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_3("do_1111111112222222222223", "do_113193462958120277239"))
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_1111111112222222222223'")
+ .one().getString("hierarchy")
+ assert(StringUtils.isNotEmpty(hierarchy))
+ })
+ }
+
+ "updateHierarchy on already existing hierarchy" should "recompose the hierarchy structure and store in in cassandra and also update neo4j" in {
+ UpdateHierarchyManager.getContentNode("do_31250856200414822416938", HierarchyConstants.TAXONOMY_ID).map(node => {
+ println("Node data from neo4j ----- id: " + node.getIdentifier + "node type: " + node.getNodeType + " node metadata : " + node.getMetadata)
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_1())
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_1())
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val identifiers = response.get(HierarchyConstants.IDENTIFIERS).asInstanceOf[util.Map[String, AnyRef]]
+ val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11294581887465881611'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(StringUtils.isNotEmpty(hierarchy))
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_2("do_11294581887465881611", identifiers.get("b9a50833-eff6-4ef5-a2a4-2413f2d51f6c").asInstanceOf[String]))
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_2("do_11294581887465881611", identifiers.get("b9a50833-eff6-4ef5-a2a4-2413f2d51f6c").asInstanceOf[String]))
+ UpdateHierarchyManager.updateHierarchy(request).map(resp => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchy_updated = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11294581887465881611'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(!StringUtils.equalsIgnoreCase(hierarchy, hierarchy_updated))
+ })
+ }).flatMap(f => f)
+ }).flatMap(f => f)
+ }
+
+ "updateHierarchy with New Unit and Invalid Resource" should "throw resource not found exception" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, getNodesModified_1())
+ request.put(HierarchyConstants.HIERARCHY, getHierarchy_Content_Resource_Invalid_ID())
+ recoverToSucceededIf[ResourceNotFoundException](UpdateHierarchyManager.updateHierarchy(request))
+ }
+
+ "updateHierarchy with empty nodes modified and hierarchy" should "throw client exception" in {
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, new util.HashMap())
+ request.put(HierarchyConstants.HIERARCHY, new util.HashMap())
+ val exception = intercept[ClientException] {
+ UpdateHierarchyManager.updateHierarchy(request)
+ }
+ exception.getMessage shouldEqual "Please Provide Valid Root Node Identifier"
+ }
+
+ "updateHierarchy test proper ordering" should "succeed with proper hierarchy structure" in {
+ val nodesModified = "{\"do_113031517435822080121\":{\"metadata\":{\"license\":\"CC BY 4.0\"},\"root\":true,\"isNew\":false},\"u1\":{\"metadata\":{\"name\":\"U1\",\"dialcodeRequired\":\"No\",\"mimeType\":\"application/vnd.ekstep.content-collection\",\"contentType\":\"CourseUnit\",\"primaryCategory\": \"Course Unit\",\"license\":\"CC BY 4.0\"},\"root\":false,\"isNew\":true},\"u2\":{\"metadata\":{\"name\":\"U2\",\"dialcodeRequired\":\"No\",\"mimeType\":\"application/vnd.ekstep.content-collection\",\"contentType\":\"CourseUnit\",\"primaryCategory\": \"Course Unit\",\"license\":\"CC BY 4.0\"},\"root\":false,\"isNew\":true}}";
+ val hierarchy = "{\"do_113031517435822080121\":{\"children\":[\"u1\",\"u2\"],\"root\":true,\"name\":\"Untitled Textbook\",\"contentType\":\"Course\",\"primaryCategory\": \"Learning Resource\"},\"u1\":{\"children\":[\"do_11303151546543308811\",\"do_11303151571010355212\",\"do_11303151584773734413\",\"do_11303151594263347214\",\"do_11303151604402585615\",\"do_11303151612719104016\",\"do_11303151623148339217\",\"do_11303151631740928018\",\"do_11303151638961356819\",\"do_113031516469411840110\"],\"root\":false,\"name\":\"U1\",\"contentType\":\"CourseUnit\",\"primaryCategory\": \"Learning Resource\"},\"u2\":{\"children\":[\"do_113031516541870080111\",\"do_113031516616491008112\",\"do_113031516693184512113\",\"do_113031516791406592114\",\"do_113031516862660608115\",\"do_113031516945334272116\",\"do_113031517024190464117\",\"do_113031517104939008118\",\"do_113031517200171008119\",\"do_113031517276520448120\"],\"root\":false,\"name\":\"U2\",\"contentType\":\"CourseUnit\",\"primaryCategory\": \"Learning Resource\"},\"do_11303151546543308811\":{\"name\":\"prad PDF Content-1\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151571010355212\":{\"name\":\"prad PDF Content-2\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151584773734413\":{\"name\":\"prad PDF Content-3\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151594263347214\":{\"name\":\"prad PDF Content-4\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151604402585615\":{\"name\":\"prad PDF Content-5\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151612719104016\":{\"name\":\"prad PDF Content-6\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151623148339217\":{\"name\":\"prad PDF Content-7\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151631740928018\":{\"name\":\"prad PDF Content-8\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_11303151638961356819\":{\"name\":\"prad PDF Content-9\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516469411840110\":{\"name\":\"prad PDF Content-10\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516541870080111\":{\"name\":\"prad PDF Content-11\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516616491008112\":{\"name\":\"prad PDF Content-12\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516693184512113\":{\"name\":\"prad PDF Content-13\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516791406592114\":{\"name\":\"prad PDF Content-14\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516862660608115\":{\"name\":\"prad PDF Content-15\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031516945334272116\":{\"name\":\"prad PDF Content-16\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031517024190464117\":{\"name\":\"prad PDF Content-17\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031517104939008118\":{\"name\":\"prad PDF Content-18\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031517200171008119\":{\"name\":\"prad PDF Content-19\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false},\"do_113031517276520448120\":{\"name\":\"prad PDF Content-20\",\"contentType\":\"Resource\",\"primaryCategory\": \"Learning Resource\",\"children\":[],\"root\":false}}";
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, JsonUtils.deserialize(nodesModified, classOf[util.HashMap[String, AnyRef]]))
+ request.put(HierarchyConstants.HIERARCHY, JsonUtils.deserialize(hierarchy, classOf[util.HashMap[String, AnyRef]]))
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val identifiers = response.get(HierarchyConstants.IDENTIFIERS).asInstanceOf[util.Map[String, AnyRef]]
+ val hierarchyResp = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_113031517435822080121'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(StringUtils.isNotEmpty(hierarchyResp))
+ val children = JsonUtils.deserialize(hierarchyResp, classOf[util.HashMap[String, AnyRef]]).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(children.get(0).get("identifier").asInstanceOf[String], identifiers.get("u1").asInstanceOf[String]))
+ val u1Children = children.get(0).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(0).get("identifier").asInstanceOf[String], "do_11303151546543308811"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(1).get("identifier").asInstanceOf[String], "do_11303151571010355212"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(2).get("identifier").asInstanceOf[String], "do_11303151584773734413"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(3).get("identifier").asInstanceOf[String], "do_11303151594263347214"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(4).get("identifier").asInstanceOf[String], "do_11303151604402585615"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(5).get("identifier").asInstanceOf[String], "do_11303151612719104016"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(6).get("identifier").asInstanceOf[String], "do_11303151623148339217"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(7).get("identifier").asInstanceOf[String], "do_11303151631740928018"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(8).get("identifier").asInstanceOf[String], "do_11303151638961356819"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(9).get("identifier").asInstanceOf[String], "do_113031516469411840110"))
+
+ assert(StringUtils.equalsIgnoreCase(children.get(1).get("identifier").asInstanceOf[String], identifiers.get("u2").asInstanceOf[String]))
+ val u2Children = children.get(1).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(0).get("identifier").asInstanceOf[String], "do_113031516541870080111"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(1).get("identifier").asInstanceOf[String], "do_113031516616491008112"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(2).get("identifier").asInstanceOf[String], "do_113031516693184512113"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(3).get("identifier").asInstanceOf[String], "do_113031516791406592114"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(4).get("identifier").asInstanceOf[String], "do_113031516862660608115"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(5).get("identifier").asInstanceOf[String], "do_113031516945334272116"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(6).get("identifier").asInstanceOf[String], "do_113031517024190464117"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(7).get("identifier").asInstanceOf[String], "do_113031517104939008118"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(8).get("identifier").asInstanceOf[String], "do_113031517200171008119"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(9).get("identifier").asInstanceOf[String], "do_113031517276520448120"))
+
+ val nodesModified = "{\"do_113031517435822080121\":{\"metadata\":{\"license\":\"CC BY 4.0\"},\"root\":true,\"isNew\":false}}";
+ val request1 = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request1.setContext(context)
+ request1.put(HierarchyConstants.NODES_MODIFIED, JsonUtils.deserialize(nodesModified, classOf[util.HashMap[String, AnyRef]]))
+ request1.put(HierarchyConstants.HIERARCHY, new util.HashMap())
+ UpdateHierarchyManager.updateHierarchy(request1).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val hierarchyResp = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_113031517435822080121'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(StringUtils.isNotEmpty(hierarchyResp))
+ val children = JsonUtils.deserialize(hierarchyResp, classOf[util.HashMap[String, AnyRef]]).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ val u1Children = children.get(0).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(0).get("identifier").asInstanceOf[String], "do_11303151546543308811"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(1).get("identifier").asInstanceOf[String], "do_11303151571010355212"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(2).get("identifier").asInstanceOf[String], "do_11303151584773734413"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(3).get("identifier").asInstanceOf[String], "do_11303151594263347214"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(4).get("identifier").asInstanceOf[String], "do_11303151604402585615"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(5).get("identifier").asInstanceOf[String], "do_11303151612719104016"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(6).get("identifier").asInstanceOf[String], "do_11303151623148339217"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(7).get("identifier").asInstanceOf[String], "do_11303151631740928018"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(8).get("identifier").asInstanceOf[String], "do_11303151638961356819"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(9).get("identifier").asInstanceOf[String], "do_113031516469411840110"))
+
+ val u2Children = children.get(1).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(0).get("identifier").asInstanceOf[String], "do_113031516541870080111"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(1).get("identifier").asInstanceOf[String], "do_113031516616491008112"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(2).get("identifier").asInstanceOf[String], "do_113031516693184512113"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(3).get("identifier").asInstanceOf[String], "do_113031516791406592114"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(4).get("identifier").asInstanceOf[String], "do_113031516862660608115"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(5).get("identifier").asInstanceOf[String], "do_113031516945334272116"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(6).get("identifier").asInstanceOf[String], "do_113031517024190464117"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(7).get("identifier").asInstanceOf[String], "do_113031517104939008118"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(8).get("identifier").asInstanceOf[String], "do_113031517200171008119"))
+ assert(StringUtils.equalsIgnoreCase(u2Children.get(9).get("identifier").asInstanceOf[String], "do_113031517276520448120"))
+ })
+ }).flatMap(f => f)
+ }
+
+ "updateHierarchy test proper ordering" should "succeed with proper hierarchy structure on same resources" in {
+ val nodesModified = "{\"do_113031517435822080121\":{\"metadata\":{},\"root\":false,\"isNew\":false},\"u1\":{\"metadata\":{\"name\":\"U1\",\"dialcodeRequired\":\"No\",\"mimeType\":\"application/vnd.ekstep.content-collection\",\"primaryCategory\": \"Content Playlist\",\"contentType\":\"Collection\",\"license\":\"CC BY 4.0\"},\"root\":false,\"isNew\":true},\"u1.1\":{\"metadata\":{\"name\":\"U1.1\",\"dialcodeRequired\":\"No\",\"mimeType\":\"application/vnd.ekstep.content-collection\",\"primaryCategory\": \"Content Playlist\",\"contentType\":\"Collection\",\"license\":\"CC BY 4.0\"},\"root\":false,\"isNew\":true}}";
+ val hierarchy = "{\"do_113031517435822080121\":{\"children\":[\"u1\"],\"root\":true,\"name\":\"CollectionTest\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Collection\"},\"u1\":{\"children\":[\"do_11303151546543308811\",\"do_11303151571010355212\",\"do_11303151584773734413\",\"do_11303151594263347214\",\"u1.1\"],\"root\":false,\"name\":\"U1\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Collection\"},\"u1.1\":{\"children\":[\"do_11303151594263347214\",\"do_11303151584773734413\",\"do_11303151571010355212\",\"do_11303151546543308811\"],\"root\":false,\"name\":\"U1.1\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Collection\"},\"do_11303151546543308811\":{\"name\":\"prad PDF Content-1\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Resource\",\"children\":[],\"root\":false},\"do_11303151571010355212\":{\"name\":\"prad PDF Content-2\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Resource\",\"children\":[],\"root\":false},\"do_11303151584773734413\":{\"name\":\"prad PDF Content-3\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Resource\",\"children\":[],\"root\":false},\"do_11303151594263347214\":{\"name\":\"prad PDF Content-4\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"Resource\",\"children\":[],\"root\":false}}";
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, JsonUtils.deserialize(nodesModified, classOf[util.HashMap[String, AnyRef]]))
+ request.put(HierarchyConstants.HIERARCHY, JsonUtils.deserialize(hierarchy, classOf[util.HashMap[String, AnyRef]]))
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val identifiers = response.get(HierarchyConstants.IDENTIFIERS).asInstanceOf[util.Map[String, AnyRef]]
+ val hierarchyResp = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_113031517435822080121'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(StringUtils.isNotEmpty(hierarchyResp))
+ val children = JsonUtils.deserialize(hierarchyResp, classOf[util.HashMap[String, AnyRef]]).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(children.get(0).get("identifier").asInstanceOf[String], identifiers.get("u1").asInstanceOf[String]))
+ val u1Children = children.get(0).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(0).get("identifier").asInstanceOf[String], "do_11303151546543308811"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(1).get("identifier").asInstanceOf[String], "do_11303151571010355212"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(2).get("identifier").asInstanceOf[String], "do_11303151584773734413"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(3).get("identifier").asInstanceOf[String], "do_11303151594263347214"))
+ assert(StringUtils.equalsIgnoreCase(u1Children.get(4).get("identifier").asInstanceOf[String], identifiers.get("u1.1").asInstanceOf[String]))
+ val u11Children = u1Children.get(4).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(u11Children.get(3).get("identifier").asInstanceOf[String], "do_11303151546543308811"))
+ assert(StringUtils.equalsIgnoreCase(u11Children.get(2).get("identifier").asInstanceOf[String], "do_11303151571010355212"))
+ assert(StringUtils.equalsIgnoreCase(u11Children.get(1).get("identifier").asInstanceOf[String], "do_11303151584773734413"))
+ assert(StringUtils.equalsIgnoreCase(u11Children.get(0).get("identifier").asInstanceOf[String], "do_11303151594263347214"))
+ })
+ }
+
+ "updateHierarchy with originData under root metadata" should "successfully store originData in root node" in {
+ val nodesModified = "{\"do_test_book_1\":{\"isNew\":true,\"root\":true,\"metadata\":{\"origin\":\"do_113000859727618048110\",\"originData\":{\"channel\":\"012983850117177344161\"}}},\"TestBookUnit-01\":{\"isNew\":true,\"root\":false,\"metadata\":{\"mimeType\":\"application/vnd.ekstep.content-collection\",\"keywords\":[],\"name\":\"U-1\",\"description\":\"U-1\",\"primaryCategory\": \"Textbook Unit\",\"contentType\":\"TextBookUnit\",\"code\":\"TestBookUnit-01\"}},\"TestBookUnit-02\":{\"isNew\":true,\"root\":false,\"metadata\":{\"mimeType\":\"application/vnd.ekstep.content-collection\",\"keywords\":[],\"name\":\"U-2\",\"description\":\"U-2\",\"primaryCategory\": \"Textbook Unit\",\"contentType\":\"TextBookUnit\",\"code\":\"TestBookUnit-02\"}}}"
+ val hierarchy = "{\"do_test_book_1\":{\"name\":\"Update Hierarchy Test For Origin Data\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"TextBook\",\"children\":[\"TestBookUnit-01\",\"TestBookUnit-02\"],\"root\":true},\"TestBookUnit-01\":{\"name\":\"U-1\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"TextBookUnit\",\"children\":[],\"root\":false},\"TestBookUnit-02\":{\"name\":\"U-2\",\"primaryCategory\": \"Learning Resource\",\"contentType\":\"TextBookUnit\",\"children\":[],\"root\":false}}"
+ val request = new Request()
+ val context = getContext()
+ context.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ request.setContext(context)
+ request.put(HierarchyConstants.NODES_MODIFIED, JsonUtils.deserialize(nodesModified, classOf[util.HashMap[String, AnyRef]]))
+ request.put(HierarchyConstants.HIERARCHY, JsonUtils.deserialize(hierarchy, classOf[util.HashMap[String, AnyRef]]))
+ UpdateHierarchyManager.updateHierarchy(request).map(response => {
+ assert(response.getResponseCode.code() == 200)
+ val identifiers = response.get(HierarchyConstants.IDENTIFIERS).asInstanceOf[util.Map[String, AnyRef]]
+ val hierarchyResp = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_test_book_1'")
+ .one().getString(HierarchyConstants.HIERARCHY)
+ assert(StringUtils.isNotEmpty(hierarchyResp))
+ val children = JsonUtils.deserialize(hierarchyResp, classOf[util.HashMap[String, AnyRef]]).get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]]
+ assert(StringUtils.equalsIgnoreCase(children.get(0).get("identifier").asInstanceOf[String], identifiers.get("TestBookUnit-01").asInstanceOf[String]))
+ val getHierarchyReq = new Request()
+ val reqContext = getContext()
+ reqContext.put(HierarchyConstants.SCHEMA_NAME, "collection")
+ getHierarchyReq.setContext(reqContext)
+ getHierarchyReq.put("rootId", "do_test_book_1")
+ getHierarchyReq.put("mode","edit")
+ val future = HierarchyManager.getHierarchy(getHierarchyReq)
+ future.map(response => {
+ assert(response.getResponseCode.code() == 200)
+ assert(null != response.getResult.get("content"))
+ val content = response.getResult.get("content").asInstanceOf[util.Map[String, AnyRef]]
+ assert(null != content.get("originData"))
+ assert(null != content.get("children"))
+ })
+ }).flatMap(f=>f)
+ }
+
+
+ //Text Book -> root, New Unit
+ def getNodesModified_1(): util.HashMap[String, AnyRef] = {
+ val nodesModifiedString: String = "{\n"+
+ " \"do_11294581887465881611\": {\n"+
+ " \"isNew\": false,\n"+
+ " \"root\": true\n"+
+ " },\n"+
+ " \"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+
+ " \"isNew\": true,\n"+
+ " \"root\": false,\n"+
+ " \"metadata\": {\n"+
+ " \"mimeType\": \"application/vnd.ekstep.content-collection\",\n"+
+ " \"contentType\": \"TextBookUnit\",\n"+
+ " \"code\": \"updateHierarchy\",\n"+
+ " \"name\": \"Test_CourseUnit_1\",\n"+
+ " \"description\": \"updated hierarchy\",\n"+
+ " \"channel\": \"in.ekstep\",\n"+
+ " \"primaryCategory\": \"Textbook Unit\"\n"+
+ " }\n"+
+ " }\n"+
+ " }"
+ JsonUtils.deserialize(nodesModifiedString, classOf[util.HashMap[String, AnyRef]])
+ }
+ //Text
+ def getHierarchy_1(): util.HashMap[String, AnyRef] = {
+ val hierarchyString = "{\n"+
+ " \t\"do_11294581887465881611\" : {\n"+
+ " \t\t\"root\": true,\n"+
+ " \t\t\"children\": [\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\"]\n"+
+ " \t},\n"+
+ " \t\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+
+ " \t\t\"root\": false,\n"+
+ " \t\t\"children\": [\"do_31250856200414822416938\"]\n"+
+ " \t}\n"+
+ " }"
+ JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])
+ }
+
+ def getNodesModified_2(rootId:String, unit_1: String): util.HashMap[String, AnyRef] = {
+ val nodesModifiedString: String = "{\n"+
+ " \""+ rootId +"\": {\n"+
+ " \"isNew\": false,\n"+
+ " \"root\": true,\n"+
+ " \"metadata\": {\n"+
+ " \"name\": \"updated text book name check\"\n"+
+ " }\n"+
+ " },\n"+
+ " \""+ unit_1 +"\": {\n"+
+ " \"isNew\": false,\n"+
+ " \"root\": false\n"+
+ " },\n"+
+ " \"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+
+ " \"isNew\": true,\n"+
+ " \"root\": false,\n"+
+ " \"metadata\": {\n"+
+ " \"mimeType\": \"application/vnd.ekstep.content-collection\",\n"+
+ " \"contentType\": \"TextBookUnit\",\n"+
+ " \"code\": \"updateHierarchy\",\n"+
+ " \"name\": \"Test_CourseUnit_1\",\n"+
+ " \"description\": \"Test_CourseUnit_desc_1\",\n"+
+ " \"primaryCategory\": \"Textbook Unit\"\n"+
+ " }\n"+
+ " }" +
+ "}"
+ JsonUtils.deserialize(nodesModifiedString, classOf[util.HashMap[String, AnyRef]])
+ }
+
+ def getHierarchy_2(rootId: String, unit_2: String): util.HashMap[String, AnyRef] = {
+ val hierarchyString: String = "{\n" +
+ " \"" + rootId + "\": {\n" +
+ " \"root\": true,\n" +
+ " \"children\": [\n" +
+ " \"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\"\n" +
+ " ]\n" +
+ " },\n" +
+ " \"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n" +
+ " \"root\": false,\n" +
+ " \"children\": [\n" +
+ " \"" + unit_2 + "\",\n" +
+ " \"do_111112224444\"\n" +
+ " ]\n" +
+ " },\n" +
+ " \"" + unit_2 + "\": {\n" +
+ " \"root\": false,\n" +
+ " \"children\": [\n" +
+ " \"do_31250856200414822416938\"\n" +
+ " ]\n" +
+ " }\n" +
+ " }"
+ JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])
+ }
+
+ //Text
+ def getHierarchy_Content_Resource_Invalid_ID(): util.HashMap[String, AnyRef] = {
+ val hierarchyString = "{\n"+
+ " \t\"do_11294581887465881611\" : {\n"+
+ " \t\t\"root\": true,\n"+
+ " \t\t\"children\": [\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\"]\n"+
+ " \t},\n"+
+ " \t\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+
+ " \t\t\"root\": false,\n"+
+ " \t\t\"children\": [\"do_3125085620041482241\"]\n"+
+ " \t}\n"+
+ " }"
+ JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])
+ }
+
+
+ def insert20NodesAndOneCourse() = {
+ graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],code:\"txtbk\",channel:\"in.ekstep\",description:\"Text Book Test\",language:[\"English\"],mimeType:\"application/vnd.ekstep.content-collection\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:40:05.744+0530\",contentDisposition:\"inline\",contentEncoding:\"gzip\",lastUpdatedOn:\"2020-05-29T23:19:49.635+0530\",contentType:\"Course\",primaryCategory:\"Course\",dialcodeRequired:\"No\",identifier:\"do_113031517435822080121\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:40:05.744+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590774589635\",license:\"CC BY 4.0\",idealScreenDensity:\"hdpi\",depth:0,framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"TextBook\",IL_UNIQUE_ID:\"do_113031517435822080121\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:16.618+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:16.618+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:16.618+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761296618\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-11\",IL_UNIQUE_ID:\"do_113031516541870080111\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:36:35.092+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:36:35.092+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:36:35.092+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761195092\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-2\",IL_UNIQUE_ID:\"do_11303151571010355212\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:25.737+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:25.737+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:25.737+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761305737\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-12\",IL_UNIQUE_ID:\"do_113031516616491008112\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:15.852+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:15.852+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:15.852+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761235852\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-5\",IL_UNIQUE_ID:\"do_11303151604402585615\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:03.470+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:03.470+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:03.470+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761223470\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-4\",IL_UNIQUE_ID:\"do_11303151594263347214\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:39:15.501+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:39:15.501+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:39:15.501+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761355501\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-17\",IL_UNIQUE_ID:\"do_113031517024190464117\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:47.078+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:47.078+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:47.078+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761327078\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-14\",IL_UNIQUE_ID:\"do_113031516791406592114\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:58.033+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:58.033+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:58.033+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761278033\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-9\",IL_UNIQUE_ID:\"do_11303151638961356819\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:39:46.297+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:39:46.297+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:39:46.297+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761386297\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-20\",IL_UNIQUE_ID:\"do_113031517276520448120\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:36:51.893+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:36:51.893+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:36:51.893+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761211893\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-3\",IL_UNIQUE_ID:\"do_11303151584773734413\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:39:25.353+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:39:25.353+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:39:25.353+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761365353\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-18\",IL_UNIQUE_ID:\"do_113031517104939008118\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:35.090+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:35.090+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:35.090+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761315090\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-13\",IL_UNIQUE_ID:\"do_113031516693184512113\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:39:05.869+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:39:05.869+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:39:05.869+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761345869\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-16\",IL_UNIQUE_ID:\"do_113031516945334272116\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:07.777+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:07.777+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:07.777+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761287777\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-10\",IL_UNIQUE_ID:\"do_113031516469411840110\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:36:08.181+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:36:08.181+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:36:08.181+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761168181\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-1\",IL_UNIQUE_ID:\"do_11303151546543308811\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:39:36.978+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:39:36.978+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:39:36.978+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761376978\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-19\",IL_UNIQUE_ID:\"do_113031517200171008119\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:25.999+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:25.999+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:25.999+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761245999\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-6\",IL_UNIQUE_ID:\"do_11303151612719104016\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:38.731+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:38.731+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:38.731+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761258731\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-7\",IL_UNIQUE_ID:\"do_11303151623148339217\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:37:49.223+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:37:49.223+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:37:49.223+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761269223\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-8\",IL_UNIQUE_ID:\"do_11303151631740928018\",status:\"Draft\"},\n{ownershipType:[\"createdBy\"],code:\"test-Resourcce\",channel:\"in.ekstep\",language:[\"English\"],mimeType:\"application/pdf\",idealScreenSize:\"normal\",createdOn:\"2020-05-29T19:38:55.782+0530\",contentDisposition:\"inline\",contentEncoding:\"identity\",lastUpdatedOn:\"2020-05-29T19:38:55.782+0530\",contentType:\"Resource\",primaryCategory:\"Learning Resource\",dialcodeRequired:\"No\",audience:[\"Student\"],lastStatusChangedOn:\"2020-05-29T19:38:55.782+0530\",os:[\"All\"],visibility:\"Default\",IL_SYS_NODE_TYPE:\"DATA_NODE\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",version:2,versionKey:\"1590761335782\",idealScreenDensity:\"hdpi\",license:\"CC BY 4.0\",framework:\"NCF\",compatibilityLevel:1,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"prad PDF Content-15\",IL_UNIQUE_ID:\"do_113031516862660608115\",status:\"Draft\"}" +
+ ",{owner:\"in.ekstep\",code:\"NCF\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"NCF\",status:\"Live\",apoc_num:1}" +
+ ",{owner:\"in.ekstep\",code:\"K-12\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"K-12\",status:\"Live\",apoc_num:1}" +
+ ",{owner:\"in.ekstep\",code:\"tpd\",IL_SYS_NODE_TYPE:\"DATA_NODE\",apoc_json:\"{\\\"batch\\\": true}\",consumerId:\"9393568c-3a56-47dd-a9a3-34da3c821638\",channel:\"in.ekstep\",description:\"NCF \",type:\"K-12\",createdOn:\"2018-01-23T09:53:50.189+0000\",versionKey:\"1545195552163\",apoc_text:\"APOC\",appId:\"dev.sunbird.portal\",IL_FUNC_OBJECT_TYPE:\"Framework\",name:\"State (Uttar Pradesh)\",lastUpdatedOn:\"2018-12-19T04:59:12.163+0000\",IL_UNIQUE_ID:\"tpd\",status:\"Live\",apoc_num:1}] as row CREATE (n:domain) SET n += row;");
+ }
+
+ def getNodesModified_3(): util.HashMap[String, AnyRef] = {
+ val nodesModifiedString: String = "{\n"+
+ " \"U1\": {\n"+
+ " \"isNew\": true,\n"+
+ " \"root\": false,\n"+
+ " \"metadata\": {\n"+
+ " \"mimeType\": \"application/vnd.ekstep.content-collection\",\n"+
+ " \"contentType\": \"TextBookUnit\",\n"+
+ " \"code\": \"updateHierarchy\",\n"+
+ " \"name\": \"U1\",\n"+
+ " \"description\": \"updated hierarchy\",\n"+
+ " \"channel\": \"in.ekstep\",\n"+
+ " \"primaryCategory\": \"Textbook Unit\"\n"+
+ " }\n"+
+ " }\n"+
+ " }"
+ JsonUtils.deserialize(nodesModifiedString, classOf[util.HashMap[String, AnyRef]])
+ }
+ def getHierarchy_3(rootId: String,childrenId: String): util.HashMap[String, AnyRef] = {
+ val hierarchyString = "{\n"+
+ " \t\""+rootId+"\" : {\n"+
+ " \t\t\"root\": true,\n"+
+ " \t\t\"children\": [\"U1\"]\n"+
+ " \t},\n"+
+ " \t\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+
+ " \t\t\"root\": false,\n"+
+ " \t\t\"children\": [\""+childrenId+"\"]\n"+
+ " \t}\n"+
+ " }"
+ JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])
+ }
+}
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java b/content-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java
similarity index 65%
rename from learning-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java
rename to content-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java
index dfb15c4c3..3642e9e7b 100644
--- a/learning-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java
+++ b/content-api/orchestrator/src/main/java/org/sunbird/actors/CollectionActor.java
@@ -4,6 +4,7 @@
import org.sunbird.common.dto.Request;
import org.sunbird.common.dto.Response;
import org.sunbird.managers.HierarchyManager;
+import org.sunbird.managers.UpdateHierarchyManager;
import scala.concurrent.Future;
public class CollectionActor extends BaseActor {
@@ -16,6 +17,8 @@ public Future onReceive(Request request) throws Throwable {
switch (operation) {
case "addHierarchy": return addLeafNodesToHierarchy(request);
case "removeHierarchy": return removeLeafNodesFromHierarchy(request);
+ case "updateHierarchy": return updateHierarchy(request);
+ case "getHierarchy": return getHierarchy(request);
default: return ERROR(operation);
}
}
@@ -29,4 +32,13 @@ private Future removeLeafNodesFromHierarchy(Request request) throws Ex
request.getContext().put("schemaName", SCHEMA_NAME);
return HierarchyManager.removeLeafNodesFromHierarchy(request, getContext().dispatcher());
}
+
+ private Future updateHierarchy(Request request) throws Exception {
+ request.getContext().put("schemaName", SCHEMA_NAME);
+ return UpdateHierarchyManager.updateHierarchy(request, getContext().dispatcher());
+ }
+ private Future getHierarchy(Request request) throws Exception {
+ request.getContext().put("schemaName", SCHEMA_NAME);
+ return HierarchyManager.getHierarchy(request, getContext().dispatcher());
+ }
}
diff --git a/content-api/orchestrator/src/test/resources/application.conf b/content-api/orchestrator/src/test/resources/application.conf
new file mode 100644
index 000000000..a07b73257
--- /dev/null
+++ b/content-api/orchestrator/src/test/resources/application.conf
@@ -0,0 +1,199 @@
+# Learning-Service Configuration
+content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit", "event"]
+
+# Cassandra Configuration
+content.keyspace.name=content_store
+content.keyspace.table=content_data
+#TODO: Add Configuration for assessment. e.g: question_data
+orchestrator.keyspace.name=script_store
+orchestrator.keyspace.table=script_data
+cassandra.lp.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+cassandra.lpa.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042"
+
+# Redis Configuration
+redis.host=localhost
+redis.port=6379
+redis.maxConnections=128
+
+#Condition to enable publish locally
+content.publish_task.enabled=true
+
+#directory location where store unzip file
+dist.directory=/data/tmp/dist/
+output.zipfile=/data/tmp/story.zip
+source.folder=/data/tmp/temp2/
+save.directory=/data/tmp/temp/
+
+# Content 2 vec analytics URL
+CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec"
+
+# FOR CONTENT WORKFLOW PIPELINE (CWP)
+
+#--Content Workflow Pipeline Mode
+OPERATION_MODE=TEST
+
+#--Maximum Content Package File Size Limit in Bytes (50 MB)
+MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800
+
+#--Maximum Asset File Size Limit in Bytes (20 MB)
+MAX_ASSET_FILE_SIZE_LIMIT=20971520
+
+#--No of Retry While File Download Fails
+RETRY_ASSET_DOWNLOAD_COUNT=1
+
+#Google-vision-API
+google.vision.tagging.enabled = false
+
+#Orchestrator env properties
+env="https://dev.ekstep.in/api/learning"
+
+#Current environment
+cloud_storage.env=dev
+
+
+#Folder configuration
+cloud_storage.content.folder=content
+cloud_storage.asset.folder=assets
+cloud_storage.artefact.folder=artifact
+cloud_storage.bundle.folder=bundle
+cloud_storage.media.folder=media
+cloud_storage.ecar.folder=ecar_files
+
+# Media download configuration
+content.media.base.url="https://dev.open-sunbird.org"
+plugin.media.base.url="https://dev.open-sunbird.org"
+
+# Configuration
+graph.dir=/data/testingGraphDB
+akka.request_timeout=30
+environment.id=10000000
+graph.ids=["domain"]
+graph.passport.key.base=31b6fd1c4d64e745c867e61a45edc34a
+route.domain="bolt://localhost:7687"
+route.bolt.write.domain="bolt://localhost:7687"
+route.bolt.read.domain="bolt://localhost:7687"
+route.bolt.comment.domain="bolt://localhost:7687"
+route.all="bolt://localhost:7687"
+route.bolt.write.all="bolt://localhost:7687"
+route.bolt.read.all="bolt://localhost:7687"
+route.bolt.comment.all="bolt://localhost:7687"
+
+shard.id=1
+platform.auth.check.enabled=false
+platform.cache.ttl=3600000
+
+# Elasticsearch properties
+search.es_conn_info="localhost:9200"
+search.fields.query=["name^100","title^100","lemma^100","code^100","tags^100","domain","subject","description^10","keywords^25","ageGroup^10","filter^10","theme^10","genre^10","objects^25","contentType^100","language^200","teachingMode^25","skills^10","learningObjective^10","curriculum^100","gradeLevel^100","developer^100","attributions^10","owner^50","text","words","releaseNotes"]
+search.fields.date=["lastUpdatedOn","createdOn","versionDate","lastSubmittedOn","lastPublishedOn"]
+search.batch.size=500
+search.connection.timeout=30
+platform-api-url="http://localhost:8080/language-service"
+MAX_ITERATION_COUNT_FOR_SAMZA_JOB=2
+
+
+# DIAL Code Configuration
+dialcode.keyspace.name="dialcode_store"
+dialcode.keyspace.table="dial_code"
+dialcode.max_count=1000
+
+# System Configuration
+system.config.keyspace.name="dialcode_store"
+system.config.table="system_config"
+
+
+#Publisher Configuration
+publisher.keyspace.name="dialcode_store"
+publisher.keyspace.table="publisher"
+
+#DIAL Code Generator Configuration
+dialcode.strip.chars="0"
+dialcode.length=6.0
+dialcode.large.prime_number=1679979167
+
+#DIAL Code ElasticSearch Configuration
+dialcode.index=true
+dialcode.object_type="DialCode"
+
+framework.max_term_creation_limit=200
+
+# Enable Suggested Framework in Get Channel API.
+channel.fetch.suggested_frameworks=true
+
+# Kafka configuration details
+kafka.topics.instruction="local.learning.job.request"
+kafka.urls="localhost:9092"
+
+#Youtube Standard Licence Validation
+learning.content.youtube.validate.license=true
+learning.content.youtube.application.name=fetch-youtube-license
+youtube.license.regex.pattern=["\\?vi?=([^&]*)", "watch\\?.*v=([^&]*)", "(?:embed|vi?)/([^/?]*)","^([A-Za-z0-9\\-\\_]*)"]
+
+#Top N Config for Search Telemetry
+telemetry_env=dev
+telemetry.search.topn=5
+
+installation.id=ekstep
+
+channel.default="in.ekstep"
+
+# DialCode Link API Config
+learning.content.link_dialcode_validation=true
+dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/search"
+dialcode.api.authorization=auth_key
+
+# Language-Code Configuration
+language.graph_ids=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"]
+
+# Kafka send event to topic enable
+kafka.topic.send.enable=false
+
+learning.valid_license=["creativeCommon"]
+learning.service_provider=["youtube"]
+
+stream.mime.type=video/mp4
+compositesearch.index.name="compositesearch"
+
+hierarchy.keyspace.name=hierarchy_store
+content.hierarchy.table=content_hierarchy
+framework.hierarchy.table=framework_hierarchy
+
+# Kafka topic for definition update event.
+kafka.topic.system.command="dev.system.command"
+
+learning.reserve_dialcode.content_type=["TextBook"]
+# restrict.metadata.objectTypes=["Content", "ContentImage", "AssessmentItem", "Channel", "Framework", "Category", "CategoryInstance", "Term"]
+
+#restrict.metadata.objectTypes="Content,ContentImage"
+
+publish.collection.fullecar.disable=true
+
+# Consistency Level for Multi Node Cassandra cluster
+cassandra.lp.consistency.level=QUORUM
+
+
+
+
+content.nested.fields="badgeAssertions,targets,badgeAssociations"
+
+content.cache.ttl=86400
+content.cache.enable=true
+collection.cache.enable=true
+content.discard.status=["Draft","FlagDraft"]
+
+framework.categories_cached=["subject", "medium", "gradeLevel", "board"]
+framework.cache.ttl=86400
+framework.cache.read=true
+
+
+# Max size(width/height) of thumbnail in pixels
+max.thumbnail.size.pixels=150
+
+schema.base_path="../../schemas/"
+content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"]
+
+collection.keyspace = "hierarchy_store"
+content.keyspace = "content_store"
+collection.image.migration.enabled=true
+objectcategorydefinition.keyspace=category_store
+
diff --git a/learning-api/pom.xml b/content-api/pom.xml
similarity index 66%
rename from learning-api/pom.xml
rename to content-api/pom.xml
index 1a0ad3564..ff480cc54 100755
--- a/learning-api/pom.xml
+++ b/content-api/pom.xml
@@ -7,23 +7,19 @@
1.0-SNAPSHOT
../pom.xml
- learning-api
+ content-api
1.0-SNAPSHOT
pom
- learning-api
+ content-api
- 2.3.1
- 1.8
- 1.8
UTF-8
UTF-8
- 1.1.1
- 2.11.8
+ 2.11.12
content-service
hierarchy-manager
- orchestrator
+ content-actors
@@ -32,22 +28,13 @@
maven-assembly-plugin
- 2.3
+ 3.3.0
src/assembly/bin.xml
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 2.3.2
-
- 1.8
- 1.8
-
-
org.scoverage
scoverage-maven-plugin
diff --git a/definition-scripts/Asset.sh b/definition-scripts/Asset.sh
new file mode 100644
index 000000000..a857b9625
--- /dev/null
+++ b/definition-scripts/Asset.sh
@@ -0,0 +1,15 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:asset",
+ "targetObjectType": "Asset",
+ "objectMetadata":{
+ "config":{},
+ "schema":{}
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/Cert_Asset.sh b/definition-scripts/Cert_Asset.sh
new file mode 100644
index 000000000..3aaf665f1
--- /dev/null
+++ b/definition-scripts/Cert_Asset.sh
@@ -0,0 +1,14 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:certasset",
+ "targetObjectType": "Asset",
+ "objectMetadata":{
+ "config":{},
+ "schema":{}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Certificate_Template.sh b/definition-scripts/Certificate_Template.sh
new file mode 100644
index 000000000..97c808da6
--- /dev/null
+++ b/definition-scripts/Certificate_Template.sh
@@ -0,0 +1,56 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:certificate-template",
+ "targetObjectType": "Asset",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "issuer": {
+ "type": "object"
+ },
+ "signatoryList": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "image": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "designation": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "logos": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "certType": {
+ "type": "string",
+ "enum": [
+ "cert template layout",
+ "cert template"
+ ]
+ },
+ "data": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Content_playlist.sh b/definition-scripts/Content_playlist.sh
new file mode 100644
index 000000000..a60e27c17
--- /dev/null
+++ b/definition-scripts/Content_playlist.sh
@@ -0,0 +1,14 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:content-playlist",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Content_playlist_content.sh b/definition-scripts/Content_playlist_content.sh
new file mode 100644
index 000000000..27ca078bd
--- /dev/null
+++ b/definition-scripts/Content_playlist_content.sh
@@ -0,0 +1,14 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:content-playlist",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Course.sh b/definition-scripts/Course.sh
new file mode 100644
index 000000000..43fe150a1
--- /dev/null
+++ b/definition-scripts/Course.sh
@@ -0,0 +1,78 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:course",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ },
+ "default": {
+ "enabled": "Yes",
+ "autoBatch": "Yes"
+ },
+ "additionalProperties": false
+ },
+ "monitorable": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "progress-report",
+ "score-report"
+ ]
+ }
+ },
+ "credentials": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ },
+ "default": {
+ "enabled": "Yes"
+ },
+ "additionalProperties": false
+ },
+ "userConsent": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/CourseUnit.sh b/definition-scripts/CourseUnit.sh
new file mode 100644
index 000000000..1329104fb
--- /dev/null
+++ b/definition-scripts/CourseUnit.sh
@@ -0,0 +1,14 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:course-unit",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/CourseUnit_content.sh b/definition-scripts/CourseUnit_content.sh
new file mode 100644
index 000000000..581d83467
--- /dev/null
+++ b/definition-scripts/CourseUnit_content.sh
@@ -0,0 +1,14 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:course-unit",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Course_assessment.sh b/definition-scripts/Course_assessment.sh
new file mode 100644
index 000000000..1cab8852d
--- /dev/null
+++ b/definition-scripts/Course_assessment.sh
@@ -0,0 +1,39 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:course-assessment",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Course_content.sh b/definition-scripts/Course_content.sh
new file mode 100644
index 000000000..9d0ba5f03
--- /dev/null
+++ b/definition-scripts/Course_content.sh
@@ -0,0 +1,75 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:course",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": ["Yes","No"],
+ "default": "Yes"
+ }
+ },
+ "default": {
+ "enabled": "Yes",
+ "autoBatch": "Yes"
+ },
+ "additionalProperties": false
+ },
+ "monitorable": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "progress-report",
+ "score-report"
+ ]
+ }
+ },
+ "credentials": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ },
+ "default": {
+ "enabled": "Yes"
+ },
+ "additionalProperties": false
+ },
+ "userConsent": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Digital_textbook.sh b/definition-scripts/Digital_textbook.sh
new file mode 100644
index 000000000..a606e5d6b
--- /dev/null
+++ b/definition-scripts/Digital_textbook.sh
@@ -0,0 +1,59 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:digital-textbook",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ },
+ "default": {
+ "enabled": "No",
+ "autoBatch": "No"
+ },
+ "additionalProperties": false
+ },
+ "additionalCategories": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": "Textbook"
+ }
+ },
+ "userConsent": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "Yes"
+ }
+ }
+ }
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/Digital_textbook_content.sh b/definition-scripts/Digital_textbook_content.sh
new file mode 100644
index 000000000..ab77d99f5
--- /dev/null
+++ b/definition-scripts/Digital_textbook_content.sh
@@ -0,0 +1,59 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:digital-textbook",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ },
+ "default": {
+ "enabled": "No",
+ "autoBatch": "No"
+ },
+ "additionalProperties": false
+ },
+ "additionalCategories": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": "Textbook"
+ }
+ },
+ "userConsent": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ }
+ }
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/Explanation_collection.sh b/definition-scripts/Explanation_collection.sh
new file mode 100644
index 000000000..cd7806260
--- /dev/null
+++ b/definition-scripts/Explanation_collection.sh
@@ -0,0 +1,30 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:explanation-content",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Explanation_content.sh b/definition-scripts/Explanation_content.sh
new file mode 100644
index 000000000..7f3ee4bed
--- /dev/null
+++ b/definition-scripts/Explanation_content.sh
@@ -0,0 +1,30 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:explanation-content",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/FTB_QUESTION.sh b/definition-scripts/FTB_QUESTION.sh
new file mode 100755
index 000000000..1b6a1d3b9
--- /dev/null
+++ b/definition-scripts/FTB_QUESTION.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+curl -L -X POST '/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:ftb-question",
+ "targetObjectType": "Question",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Learning_resource.sh b/definition-scripts/Learning_resource.sh
new file mode 100644
index 000000000..ae8aa2434
--- /dev/null
+++ b/definition-scripts/Learning_resource.sh
@@ -0,0 +1,14 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:learning-resource",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/LessonPlan_unit.sh b/definition-scripts/LessonPlan_unit.sh
new file mode 100755
index 000000000..fe9f31dd5
--- /dev/null
+++ b/definition-scripts/LessonPlan_unit.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:lesson-plan-unit",
+ "targetObjectType": "Collection",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/LessonPlan_unit_content.sh b/definition-scripts/LessonPlan_unit_content.sh
new file mode 100755
index 000000000..80fc0e16b
--- /dev/null
+++ b/definition-scripts/LessonPlan_unit_content.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:lesson-plan-unit",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Multiple_Choice_Question.sh b/definition-scripts/Multiple_Choice_Question.sh
new file mode 100755
index 000000000..8a4862367
--- /dev/null
+++ b/definition-scripts/Multiple_Choice_Question.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:multiple-choice-question",
+ "targetObjectType": "Question",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Plugin_Content.sh b/definition-scripts/Plugin_Content.sh
new file mode 100644
index 000000000..e107ab4cc
--- /dev/null
+++ b/definition-scripts/Plugin_Content.sh
@@ -0,0 +1,15 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:plugin",
+ "targetObjectType": "Content",
+ "objectMetadata":{
+ "config":{},
+ "schema":{}
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/Subjective_Question.sh b/definition-scripts/Subjective_Question.sh
new file mode 100755
index 000000000..0f723e657
--- /dev/null
+++ b/definition-scripts/Subjective_Question.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:subjective-question",
+ "targetObjectType": "Question",
+ "objectMetadata": {
+ "config": {},
+ "schema": {}
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Teacher_resource.sh b/definition-scripts/Teacher_resource.sh
new file mode 100644
index 000000000..cc87aaa09
--- /dev/null
+++ b/definition-scripts/Teacher_resource.sh
@@ -0,0 +1,39 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:teacher-resource",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/Template_Content.sh b/definition-scripts/Template_Content.sh
new file mode 100644
index 000000000..d2bb45ca5
--- /dev/null
+++ b/definition-scripts/Template_Content.sh
@@ -0,0 +1,15 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:template",
+ "targetObjectType": "Content",
+ "objectMetadata":{
+ "config":{},
+ "schema":{}
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/TextBookUnit.sh b/definition-scripts/TextBookUnit.sh
new file mode 100644
index 000000000..4c7d49a00
--- /dev/null
+++ b/definition-scripts/TextBookUnit.sh
@@ -0,0 +1,30 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:textbook-unit",
+ "targetObjectType": "Collection",
+ "objectMetadata":{
+ "config":{},
+ "schema":{
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": ["Yes","No"],
+ "default": "No"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+
+ }
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/TextBookUnit_content.sh b/definition-scripts/TextBookUnit_content.sh
new file mode 100644
index 000000000..04af2b93c
--- /dev/null
+++ b/definition-scripts/TextBookUnit_content.sh
@@ -0,0 +1,30 @@
+curl --location --request POST '{{host}}/object/category/definition/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request":{
+ "objectCategoryDefinition":{
+ "categoryId": "obj-cat:textbook-unit",
+ "targetObjectType": "Content",
+ "objectMetadata":{
+ "config":{},
+ "schema":{
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": ["Yes","No"],
+ "default": "No"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+
+ }
+ }
+ }
+ }
+}'
+
diff --git a/definition-scripts/eTextbook.sh b/definition-scripts/eTextbook.sh
new file mode 100644
index 000000000..196096617
--- /dev/null
+++ b/definition-scripts/eTextbook.sh
@@ -0,0 +1,37 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:etextbook",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ }
+ },
+ "additionalCategories": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": "Textbook"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/master_category_create b/definition-scripts/master_category_create
new file mode 100644
index 000000000..93913a504
--- /dev/null
+++ b/definition-scripts/master_category_create
@@ -0,0 +1,357 @@
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Textbook Unit",
+ "description":"Textbook Unit"
+ }
+ }
+}'
+
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Course Unit",
+ "description":"Course Unit"
+ }
+ }
+}'
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Lesson Plan Unit",
+ "description":"Lesson Plan Unit"
+ }
+ }
+}'
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Plugin",
+ "description":"Plugin"
+ }
+ }
+}'
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Asset",
+ "description":"Asset"
+ }
+ }
+}'
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Template",
+ "description":"Template"
+ }
+ }
+}'
+
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Content Playlist",
+ "description":"Content Playlist"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Digital Textbook",
+ "description":"Digital Textbook"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Course Assessment",
+ "description":"Course Assessment"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Teacher Resource",
+ "description":"Teacher Resource"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "eTextbook",
+ "description":"eTextbook"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Practice Question Set",
+ "description":"Practice Question Set"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Course",
+ "description":"Course"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Learning Resource",
+ "description":"Learning Resource"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Explanation Content",
+ "description":"Explanation Content"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Certificate Template",
+ "description":"Certificate Template"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "CertAsset",
+ "description":"CertAsset"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Classroom Teaching Video",
+ "description":"Classroom Teaching Video"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Concept Map",
+ "description":"Concept Map"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Curiosity Question Set",
+ "description":"Curiosity Question Set"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Experiential Resource",
+ "description":"Experiential Resource"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Explanation Video",
+ "description":"Explanation Video"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Focus Spot",
+ "description":"Focus Spot"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Learning Outcome Definition",
+ "description":"Learning Outcome Definition"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Lesson Plan",
+ "description":"Lesson Plan"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Marking Scheme Rubric",
+ "description":"Marking Scheme Rubric"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Pedagogy Flow",
+ "description":"Pedagogy Flow"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Previous Board Exam Papers",
+ "description":"Previous Board Exam Papers"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "TV Lesson",
+ "description":"TV Lesson"
+ }
+ }
+}'
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Textbook",
+ "description":"Textbook"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Multiple Choice Question",
+ "description":"Multiple Choice Question"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "Subjective Question",
+ "description":"Subjective Question"
+ }
+ }
+}'
+
+curl --location --request POST '{{host}}/object/category/v4/create' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategory": {
+ "name": "FTB Question",
+ "description":"FTB Question"
+ }
+ }
+}'
\ No newline at end of file
diff --git a/definition-scripts/practice_question_set.sh b/definition-scripts/practice_question_set.sh
new file mode 100644
index 000000000..0f530aa35
--- /dev/null
+++ b/definition-scripts/practice_question_set.sh
@@ -0,0 +1,40 @@
+curl -L -X POST '{{host}}/object/category/definition/v4/create' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "request": {
+ "objectCategoryDefinition": {
+ "categoryId": "obj-cat:practice-question-set",
+ "targetObjectType": "Content",
+ "objectMetadata": {
+ "config": {},
+ "schema": {
+ "properties": {
+ "trackable": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No"
+ ],
+ "default": "No"
+ },
+ "autoBatch": {
+ "type": "string",
+ "enum": ["Yes","No"],
+ "default": "No"
+ }
+ },
+ "default": {
+ "enabled": "No",
+ "autoBatch": "No"
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+ }
+ }
+}'
\ No newline at end of file
diff --git a/Jenkinsfile b/functional-tests/content/Jenkinsfile
similarity index 52%
rename from Jenkinsfile
rename to functional-tests/content/Jenkinsfile
index 6621bc098..0923a5ae9 100644
--- a/Jenkinsfile
+++ b/functional-tests/content/Jenkinsfile
@@ -8,46 +8,29 @@ node('build-slave') {
ansiColor('xterm') {
stage('Checkout') {
- if (!env.hub_org) {
- println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL)
- error 'Please resolve the errors and rerun..'
- } else
- println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL)
- }
cleanWs()
if (params.github_release_tag == "") {
checkout scm
commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
branch_name = sh(script: 'git name-rev --name-only HEAD | rev | cut -d "/" -f1| rev', returnStdout: true).trim()
- build_tag = branch_name + "_" + commit_hash
+ build_tag = branch_name + "_" + commit_hash + "_" + env.BUILD_NUMBER
println(ANSI_BOLD + ANSI_YELLOW + "github_release_tag not specified, using the latest commit hash: " + commit_hash + ANSI_NORMAL)
} else {
def scmVars = checkout scm
checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$params.github_release_tag"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]]
- build_tag = params.github_release_tag
+ build_tag = params.github_release_tag + "_" + env.BUILD_NUMBER
println(ANSI_BOLD + ANSI_YELLOW + "github_release_tag specified, building from tag: " + params.github_release_tag + ANSI_NORMAL)
}
echo "build_tag: " + build_tag
- stage('Build') {
- env.NODE_ENV = "build"
- print "Environment will be : ${env.NODE_ENV}"
- sh 'mvn clean install -DskipTests=true '
-
- }
+ stage('Run functional testcases') {
+ sh '''
+ echo "Running content service Functional testcases"
+ '''
- stage('Package') {
- dir('learning-api') {
- sh 'mvn play2:dist -pl content-service'
- }
- sh('chmod 777 ./build.sh')
- sh("./build.sh ${build_tag} ${env.NODE_NAME} ${hub_org}")
- }
- stage('ArchiveArtifacts') {
- archiveArtifacts "metadata.json"
- currentBuild.description = "${build_tag}"
}
- }
+ }
+ }
}
catch (err) {
currentBuild.result = "FAILURE"
diff --git a/learning-api/content-service/app/Module.scala b/learning-api/content-service/app/Module.scala
deleted file mode 100644
index 1636bc7e7..000000000
--- a/learning-api/content-service/app/Module.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-
-import com.google.inject.AbstractModule
-import org.sunbird.actors.{CollectionActor, ContentActor, HealthActor, LicenseActor}
-import play.libs.akka.AkkaGuiceSupport
-import utils.ActorNames
-
-class Module extends AbstractModule with AkkaGuiceSupport {
-
- override def configure() = {
- super.configure()
- bindActor(classOf[HealthActor], ActorNames.HEALTH_ACTOR)
- bindActor(classOf[ContentActor], ActorNames.CONTENT_ACTOR)
- bindActor(classOf[LicenseActor], ActorNames.LICENSE_ACTOR)
- bindActor(classOf[CollectionActor], ActorNames.COLLECTION_ACTOR)
- println("Initialized application actors...")
- }
-}
diff --git a/learning-api/content-service/app/controllers/HealthController.scala b/learning-api/content-service/app/controllers/HealthController.scala
deleted file mode 100644
index 3a31c56a2..000000000
--- a/learning-api/content-service/app/controllers/HealthController.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-package controllers
-
-import akka.actor.{ActorRef, ActorSystem}
-import javax.inject._
-import play.api.mvc._
-import utils.{ActorNames, ApiId}
-
-import scala.concurrent.duration._
-import scala.concurrent.{ExecutionContext, Future, Promise}
-
-class HealthController @Inject()(@Named(ActorNames.HEALTH_ACTOR) healthActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
-
- def health() = Action.async { implicit request =>
- getResult(ApiId.APPLICATION_HEALTH, healthActor, new org.sunbird.common.dto.Request())
- }
-}
diff --git a/learning-api/content-service/app/controllers/v3/ContentController.scala b/learning-api/content-service/app/controllers/v3/ContentController.scala
deleted file mode 100644
index 47c548e25..000000000
--- a/learning-api/content-service/app/controllers/v3/ContentController.scala
+++ /dev/null
@@ -1,80 +0,0 @@
-package controllers.v3
-
-import akka.actor.{ActorRef, ActorSystem}
-import com.google.inject.Singleton
-import controllers.BaseController
-import javax.inject.{Inject, Named}
-import org.sunbird.telemetry.logger.TelemetryManager
-import play.api.mvc.ControllerComponents
-import utils.{ActorNames, ApiId}
-
-import scala.collection.JavaConversions._
-import scala.concurrent.ExecutionContext
-
-@Singleton
-class ContentController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor: ActorRef,@Named(ActorNames.COLLECTION_ACTOR) collectionActor: ActorRef, cc: ControllerComponents, actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends BaseController(cc) {
-
- val objectType = "Content"
- val schemaName: String = "content"
- val version = "1.0"
-
- def create() = Action.async { implicit request =>
- val headers = commonHeaders()
- val body = requestBody()
- val content = body.getOrElse("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
- content.putAll(headers)
- val contentRequest = getRequest(content, headers, "createContent")
- setRequestContext(contentRequest, version, objectType, schemaName)
- getResult(ApiId.CREATE_CONTENT, contentActor, contentRequest)
- }
-
- /**
- * This Api end point takes 3 parameters
- * Content Identifier the unique identifier of a content
- * Mode in which the content can be viewed (default read or edit)
- * Fields are metadata that should be returned to visualize
- * @param identifier
- * @param mode
- * @param fields
- * @return
- */
- def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
- val headers = commonHeaders()
- val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
- content.putAll(headers)
- content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asInstanceOf[Map[String, Object]])
- val readRequest = getRequest(content, headers, "readContent")
- setRequestContext(readRequest, version, objectType, schemaName)
- getResult(ApiId.READ_CONTENT, contentActor, readRequest)
- }
-
- def update(identifier:String) = Action.async { implicit request =>
- val headers = commonHeaders()
- val body = requestBody()
- val content = body.getOrElse("content", new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]];
- content.putAll(headers)
- val contentRequest = getRequest(content, headers, "updateContent")
- setRequestContext(contentRequest, version, objectType, schemaName)
- contentRequest.getContext.put("identifier",identifier);
- getResult(ApiId.UPDATE_CONTENT, contentActor, contentRequest)
- }
-
- def addHierarchy() = Action.async { implicit request =>
- val headers = commonHeaders()
- val body = requestBody()
- body.putAll(headers)
- val contentRequest = getRequest(body, headers, "addHierarchy")
- setRequestContext(contentRequest, version, objectType, schemaName)
- getResult(ApiId.ADD_HIERARCHY, collectionActor, contentRequest)
- }
-
- def removeHierarchy() = Action.async { implicit request =>
- val headers = commonHeaders()
- val body = requestBody()
- body.putAll(headers)
- val contentRequest = getRequest(body, headers, "removeHierarchy")
- setRequestContext(contentRequest, version, objectType, schemaName)
- getResult(ApiId.REMOVE_HIERARCHY, collectionActor, contentRequest)
- }
-
-}
diff --git a/learning-api/content-service/app/filters/AccessLogFilter.scala b/learning-api/content-service/app/filters/AccessLogFilter.scala
deleted file mode 100644
index f28990033..000000000
--- a/learning-api/content-service/app/filters/AccessLogFilter.scala
+++ /dev/null
@@ -1,57 +0,0 @@
-package filters
-
-import akka.util.ByteString
-import javax.inject.Inject
-import org.sunbird.telemetry.util.TelemetryAccessEventUtil
-import play.api.Logging
-import play.api.libs.streams.Accumulator
-import play.api.mvc._
-import play.core.server.akkahttp.AkkaHeadersWrapper
-
-import scala.concurrent.ExecutionContext
-import scala.collection.JavaConverters._
-
-class AccessLogFilter @Inject()(implicit ec: ExecutionContext) extends EssentialFilter with Logging {
- def apply(nextFilter: EssentialAction) = new EssentialAction {
- def apply(requestHeader: RequestHeader) = {
-
- val startTime = System.currentTimeMillis
-
- val accumulator: Accumulator[ByteString, Result] = nextFilter(requestHeader)
-
- accumulator.map { result =>
- val endTime = System.currentTimeMillis
- val requestTime = endTime - startTime
-
- val path = requestHeader.headers.asInstanceOf[AkkaHeadersWrapper].request.uri.toString();
- if(!path.contains("/health")){
- var data:Map[String, Any] = Map[String, Any]()
- data += ("StartTime" -> startTime)
- data += ("env" -> "content")
- data += ("RemoteAddress" -> requestHeader.remoteAddress)
- data += ("ContentLength" -> result.body.contentLength.getOrElse(0))
- data += ("Status" -> result.header.status)
- data += ("Protocol" -> requestHeader.headers.asInstanceOf[AkkaHeadersWrapper].request.protocol)
- data += ("path" -> path)
- data += ("Method" -> requestHeader.method.toString)
- val headers = requestHeader.headers.asInstanceOf[AkkaHeadersWrapper].headers.groupBy(_._1).mapValues(_.map(_._2));
- if(None != headers.get("X-Session-ID"))
- data += ("X-Session-ID" -> headers.get("X-Session-ID").head.head)
- if(None != headers.get("X-Consumer-ID"))
- data += ("X-Consumer-ID" -> headers.get("X-Consumer-ID").head.head)
- if(None != headers.get("X-Device-ID"))
- data += ("X-Device-ID" -> headers.get("X-Device-ID").head.head)
- if(None != headers.get("X-App-Id"))
- data += ("APP_ID" -> headers.get("X-App-Id").head.head)
- if(None != headers.get("X-Authenticated-Userid"))
- data += ("X-Authenticated-Userid" -> headers.get("X-Authenticated-Userid").head.head)
- if(None != headers.get("X-Channel-Id"))
- data += ("X-Channel-Id" -> headers.get("X-Channel-Id").head.head)
-
- TelemetryAccessEventUtil.writeTelemetryEventLog(data.asInstanceOf[Map[String, AnyRef]].asJava)
- }
- result.withHeaders("Request-Time" -> requestTime.toString)
- }
- }
- }
- }
\ No newline at end of file
diff --git a/learning-api/content-service/app/utils/ActorNames.scala b/learning-api/content-service/app/utils/ActorNames.scala
deleted file mode 100644
index 6e0a22cf6..000000000
--- a/learning-api/content-service/app/utils/ActorNames.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package utils
-
-object ActorNames {
-
- final val HEALTH_ACTOR = "healthActor"
- final val CONTENT_ACTOR = "contentActor"
- final val LICENSE_ACTOR = "licenseActor"
- final val COLLECTION_ACTOR = "collectionActor"
-
-}
diff --git a/learning-api/content-service/app/utils/ApiId.scala b/learning-api/content-service/app/utils/ApiId.scala
deleted file mode 100644
index a8c9d184f..000000000
--- a/learning-api/content-service/app/utils/ApiId.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package utils
-
-object ApiId {
-
- final val APPLICATION_HEALTH = "api.content-service.health"
-
- //Content APIs
- final val CREATE_CONTENT = "ekstep.learning.content.create"
- final val READ_CONTENT = "ekstep.content.find"
- final val UPDATE_CONTENT = "ekstep.learning.content.update"
-
- // Collection APIs
- val ADD_HIERARCHY = "api.content.hierarchy.add"
- val REMOVE_HIERARCHY = "api.content.hierarchy.remove"
-
- //LicenseAPIS
- final val CREATE_LICENSE = "api.license.create"
- final val READ_LICENSE = "api.license.read"
- final val UPDATE_LICENSE = "api.license.update"
- final val RETIRE_LICENSE = "api.license.retire"
-}
diff --git a/learning-api/content-service/conf/routes b/learning-api/content-service/conf/routes
deleted file mode 100644
index 68ef9c837..000000000
--- a/learning-api/content-service/conf/routes
+++ /dev/null
@@ -1,17 +0,0 @@
-# Routes
-# This file defines all application routes (Higher priority routes first)
-# ~~~~
-GET /health controllers.HealthController.health
-
-POST /content/v3/create controllers.v3.ContentController.create
-PATCH /content/v3/update/:identifier controllers.v3.ContentController.update(identifier:String)
-GET /content/v3/read/:identifier controllers.v3.ContentController.read(identifier:String, mode:Option[String], fields:Option[String])
-PATCH /content/v3/hierarchy/add controllers.v3.ContentController.addHierarchy
-DELETE /content/v3/hierarchy/remove controllers.v3.ContentController.removeHierarchy
-
-#These are routes for License Creation
-POST /license/v3/create controllers.v3.LicenseController.create
-GET /license/v3/read/:identifier controllers.v3.LicenseController.read(identifier: String, fields:Option[String])
-PATCH /license/v3/update/:identifier controllers.v3.LicenseController.update(identifier: String)
-DELETE /license/v3/retire/:identifier controllers.v3.LicenseController.retire(identifier: String)
-
diff --git a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala b/learning-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
deleted file mode 100644
index c7676e49c..000000000
--- a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala
+++ /dev/null
@@ -1,267 +0,0 @@
-package org.sunbird.managers
-
-import java.util
-import java.util.concurrent.CompletionException
-
-import org.apache.commons.lang3.StringUtils
-import org.sunbird.common.dto.{Request, Response, ResponseHandler}
-import org.sunbird.common.exception.{ClientException, ErrorCodes, ResponseCode}
-import org.sunbird.common.{JsonUtils, Platform}
-import org.sunbird.graph.dac.model.Node
-import org.sunbird.graph.external.ExternalPropsManager
-import org.sunbird.graph.nodes.DataNode
-import org.sunbird.utils.{NodeUtil, ScalaJsonUtils}
-
-import scala.collection.JavaConversions._
-import scala.collection.JavaConverters
-import scala.concurrent.{ExecutionContext, Future}
-
-object HierarchyManager {
-
- val schemaName: String = "collection"
- val imgSuffix: String = ".img"
- val keyTobeRemoved = {
- if(Platform.config.hasPath("content.hierarchy.removed_props_for_leafNodes"))
- Platform.config.getStringList("content.hierarchy.removed_props_for_leafNodes")
- else
- java.util.Arrays.asList("collections","children","usedByContent","item_sets","methods","libraries","editorState")
- }
-
- @throws[Exception]
- def addLeafNodesToHierarchy(request:Request)(implicit ec: ExecutionContext): Future[Response] = {
- validateRequest(request)
- val rootNodeFuture = getRootNode(request)
- rootNodeFuture.map(rootNode => {
- val unitId = request.get("unitId").asInstanceOf[String]
- val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes"), schemaName)
- if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
- Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "unitId " + unitId + " does not exist")}
- }else {
- val hierarchyFuture = fetchHierarchy(request)
- hierarchyFuture.map(hierarchy => {
- if(hierarchy.isEmpty){
- Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
- } else {
- val leafNodesFuture = fetchLeafNodes(request)
- leafNodesFuture.map(leafNodes => {
- val updateResponse = updateHierarchy(unitId, hierarchy, leafNodes, rootNode, request, "add")
- updateResponse.map(response => {
- if(!ResponseHandler.checkError(response)) {
- updateRootNode(rootNode, request, "add").map(node => {
- val resp: Response = ResponseHandler.OK
- resp.put("rootId", rootNode.getIdentifier)
- resp.put(unitId, request.get("children"))
- resp
- })
- } else {
- Future { response }
- }
- }).flatMap(f => f)
- }).flatMap(f => f)
- }
- }).flatMap(f => f)
- }
- }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
- }
-
- @throws[Exception]
- def removeLeafNodesFromHierarchy(request: Request)(implicit ec: ExecutionContext): Future[Response] = {
- validateRequest(request)
- val rootNodeFuture = getRootNode(request)
- rootNodeFuture.map(rootNode => {
- val unitId = request.get("unitId").asInstanceOf[String]
- val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList("childNodes"), schemaName)
- if(!rootNodeMap.get("childNodes").asInstanceOf[Array[String]].toList.contains(unitId)) {
- Future{ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "unitId " + unitId + " does not exist")}
- }else {
- val hierarchyFuture = fetchHierarchy(request)
- hierarchyFuture.map(hierarchy => {
- if(hierarchy.isEmpty){
- Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")}
- } else {
- val updateResponse = updateHierarchy(unitId, hierarchy, null, rootNode, request, "remove")
- updateResponse.map(response => {
- if(!ResponseHandler.checkError(response)) {
- updateRootNode(rootNode, request, "remove").map(node => {
- val resp: Response = ResponseHandler.OK
- resp.put("rootId", rootNode.getIdentifier)
- resp
- })
- } else {
- Future { response }
- }
- }).flatMap(f => f)
- }
- }).flatMap(f => f)
- }
- }).flatMap(f => f) recoverWith {case e: CompletionException => throw e.getCause}
- }
-
-
- def validateRequest(request: Request)(implicit ec: ExecutionContext) = {
- val rootId = request.get("rootId").asInstanceOf[String]
- val unitId = request.get("unitId").asInstanceOf[String]
- val children = request.get("children").asInstanceOf[java.util.List[String]]
-
- if(StringUtils.isBlank(rootId)){
- throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "rootId is mandatory")
- }
- if(StringUtils.isBlank(unitId)){
- throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "unitId is mandatory")
- }
- if(null == children || children.isEmpty){
- throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "children are mandatory")
- }
- }
-
- private def getRootNode(request: Request)(implicit ec: ExecutionContext): Future[Node] = {
- val req = new Request(request)
- req.put("identifier", request.get("rootId").asInstanceOf[String])
- req.put("mode", "edit")
- DataNode.read(req)
- }
-
- def fetchHierarchy(request: Request)(implicit ec: ExecutionContext): Future[Map[String, AnyRef]] = {
- val req = new Request(request)
- req.put("identifier", request.get("rootId").asInstanceOf[String] + imgSuffix)
- val responseFuture = ExternalPropsManager.fetchProps(req, List("hierarchy"))
- responseFuture.map(response => {
- if(!ResponseHandler.checkError(response)) {
- val hierarchyString = response.getResult.toMap.getOrElse("hierarchy", "").asInstanceOf[String]
- if(!hierarchyString.isEmpty)
- JsonUtils.deserialize(hierarchyString, classOf[java.util.Map[String, AnyRef]]).toMap
- else
- Map[String, AnyRef]()
- } else {
- Map[String, AnyRef]()
- }
- })
- }
-
- def fetchLeafNodes(request: Request)(implicit ec: ExecutionContext): Future[List[Node]] = {
- val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
- val req = new Request(request)
- req.put("identifiers", leafNodes)
- val nodes = DataNode.list(req).map(nodes => {
- if(nodes.size() != leafNodes.size()) {
- val filteredList = leafNodes.toList.filter(id => !nodes.contains(id))
- throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Children which are not available are: " + leafNodes)
- }
- else nodes.toList
- })
- nodes
- }
-
- def convertNodeToMap(leafNodes: List[Node]): java.util.List[java.util.Map[String, AnyRef]] = {
- leafNodes.map(node => {
- val nodeMap:java.util.Map[String,AnyRef] = NodeUtil.serialize(node, null, schemaName)
- nodeMap.keySet().removeAll(keyTobeRemoved)
- nodeMap
- })
- }
-
- def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String]): Unit = {
- val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
- if(null != childNodes && !childNodes.isEmpty){
- val child = childNodes.get(0)
- val childList = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]]
- val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, (child.get("depth").asInstanceOf[Integer] + 1), unitId)
- child.put("children", restructuredChildren)
- } else {
- for(child <- children) {
- if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
- addChildrenToUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodes, leafNodeIds)
- }
- }
- }
-
- def removeChildrenFromUnit(children: java.util.List[java.util.Map[String, AnyRef]], unitId: String, leafNodeIds: java.util.List[String]):Unit = {
- val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList
- if(null != childNodes && !childNodes.isEmpty){
- val child = childNodes.get(0)
- if(null != child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) {
- var filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].filter(existingLeafNode => {
- !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
- })
- var index: Integer = 1
- filteredLeafNodes.toList.sortBy(x => x.get("index").asInstanceOf[Integer]).foreach(node => {
- node.put("index", index)
- index += 1
- })
- child.put("children", filteredLeafNodes)
- }
- } else {
- for(child <- children) {
- if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty)
- removeChildrenFromUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodeIds)
- }
- }
- }
-
- def updateRootNode(rootNode: Node, request: Request, operation: String)(implicit ec: ExecutionContext) = {
- val req = new Request(request)
- val leafNodes = request.get("children").asInstanceOf[java.util.List[String]]
- var childNodes = new java.util.ArrayList[String]()
- childNodes.addAll(rootNode.getMetadata.get("childNodes").asInstanceOf[Array[String]].toList)
- if(operation.equalsIgnoreCase("add"))
- childNodes.addAll(leafNodes)
- if(operation.equalsIgnoreCase("remove"))
- childNodes.removeAll(leafNodes)
- req.put("childNodes", childNodes.distinct.toArray)
- req.getContext.put("identifier", rootNode.getIdentifier.replaceAll(imgSuffix, ""))
- req.getContext.put("skipValidation", java.lang.Boolean.TRUE)
- DataNode.update(req)
- }
-
- def updateHierarchy(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit ec: ExecutionContext) = {
- val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]]
- val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]]
- if("add".equalsIgnoreCase(operation)){
- val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes)
- addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds)
- }
- if("remove".equalsIgnoreCase(operation)) {
- removeChildrenFromUnit(children,unitId, leafNodeIds)
- }
- val updatedHierarchy = new java.util.HashMap[String, AnyRef]()
- updatedHierarchy.putAll(hierarchy)
- updatedHierarchy.put("children", children)
- val req = new Request(request)
- req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy))
- req.put("identifier", rootNode.getIdentifier.replaceAll(imgSuffix, "") + imgSuffix)
- ExternalPropsManager.saveProps(req)
- }
-
- def restructureUnit(childList: java.util.List[java.util.Map[String, AnyRef]], leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], depth: Integer, parent: String): java.util.List[java.util.Map[String, AnyRef]] = {
- var maxIndex:Integer = 0
- var leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]()
- for(leafNode <- leafNodes){
- leafNodeMap.put(leafNode.get("identifier").asInstanceOf[String], JavaConverters.mapAsJavaMapConverter(leafNode).asJava)
- }
- var filteredLeafNodes: java.util.List[java.util.Map[String, AnyRef]] = new util.ArrayList[java.util.Map[String, AnyRef]]()
- if(null != childList && !childList.isEmpty) {
- val childMap:Map[String, java.util.Map[String, AnyRef]] = childList.toList.map(f => f.get("identifier").asInstanceOf[String] -> f).toMap
- val existingLeafNodes = childMap.filter(p => leafNodeIds.contains(p._1))
- existingLeafNodes.map(en => {
- leafNodeMap.get(en._1).put("index", en._2.get("index").asInstanceOf[Integer])
- })
- filteredLeafNodes = bufferAsJavaList(childList.filter(existingLeafNode => {
- !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String])
- }))
- maxIndex = childMap.values.toList.map(child => child.get("index").asInstanceOf[Integer]).toList.max.asInstanceOf[Integer]
- }
- leafNodeIds.foreach(id => {
- var node = leafNodeMap.get(id)
- node.put("parent", parent)
- node.put("depth", depth)
- if( null == node.get("index")) {
- val index:Integer = maxIndex + 1
- node.put("index", index)
- maxIndex += 1
- }
- filteredLeafNodes.add(node)
- })
- filteredLeafNodes
- }
-
-}
diff --git a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala b/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala
deleted file mode 100644
index 5a04a5bda..000000000
--- a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/NodeUtil.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.sunbird.utils
-
-import java.util
-import java.util.Map
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.scala.DefaultScalaModule
-import org.apache.commons.collections4.CollectionUtils
-import org.sunbird.graph.dac.model.{Node, Relation}
-import org.sunbird.graph.schema.DefinitionNode
-
-import scala.collection.JavaConverters._
-
-object NodeUtil {
- val mapper: ObjectMapper = new ObjectMapper()
- mapper.registerModule(DefaultScalaModule)
-
- def serialize(node: Node, fields: util.List[String], schemaName: String): util.Map[String, AnyRef] = {
- val metadataMap = node.getMetadata
- metadataMap.put("identifier", node.getIdentifier)
- if (CollectionUtils.isNotEmpty(fields))
- metadataMap.keySet.retainAll(fields)
- val jsonProps = DefinitionNode.fetchJsonProps(node.getGraphId, "1.0", schemaName)
- val updatedMetadataMap:util.Map[String, AnyRef] = metadataMap.entrySet().asScala.filter(entry => null != entry.getValue).map((entry: util.Map.Entry[String, AnyRef]) => handleKeyNames(entry, fields) -> convertJsonProperties(entry, jsonProps)).toMap.asJava
- val definitionMap = DefinitionNode.getRelationDefinitionMap(node.getGraphId, "1.0", schemaName).asJava
- val relMap:util.Map[String, util.List[util.Map[String, AnyRef]]] = getRelationMap(node, updatedMetadataMap, definitionMap)
- var finalMetadata = new util.HashMap[String, AnyRef]()
- finalMetadata.putAll(updatedMetadataMap)
- finalMetadata.putAll(relMap)
- finalMetadata
- }
-
- def handleKeyNames(entry: Map.Entry[String, AnyRef], fields: util.List[String]) = {
- if(CollectionUtils.isEmpty(fields)) {
- entry.getKey.substring(0,1) + entry.getKey.substring(1)
- } else {
- entry.getKey
- }
- }
-
- def getRelationMap(node: Node, updatedMetadataMap: util.Map[String, AnyRef], relationMap: util.Map[String, AnyRef]):util.Map[String, util.List[util.Map[String, AnyRef]]] = {
- val inRelations:util.List[Relation] = { if (CollectionUtils.isEmpty(node.getInRelations)) new util.ArrayList[Relation] else node.getInRelations }
- val outRelations:util.List[Relation] = { if (CollectionUtils.isEmpty(node.getOutRelations)) new util.ArrayList[Relation] else node.getOutRelations }
- val relMap = new util.HashMap[String, util.List[util.Map[String, AnyRef]]]
- for (rel <- inRelations.asScala) {
- if (relMap.containsKey(relationMap.get(rel.getRelationType + "_in_" + rel.getStartNodeObjectType))) relMap.get(relationMap.get(rel.getRelationType + "_in_" + rel.getStartNodeObjectType)).add(populateRelationMaps(rel, "in"))
- else {
- if(null != relationMap.get(rel.getRelationType + "_in_" + rel.getStartNodeObjectType)) {
- relMap.put(relationMap.get(rel.getRelationType + "_in_" + rel.getStartNodeObjectType).asInstanceOf[String], new util.ArrayList[util.Map[String, AnyRef]]() {})
- }
- }
- }
- for (rel <- outRelations.asScala) {
- if (relMap.containsKey(relationMap.get(rel.getRelationType + "_out_" + rel.getEndNodeObjectType))) relMap.get(relationMap.get(rel.getRelationType + "_out_" + rel.getEndNodeObjectType)).add(populateRelationMaps(rel, "out"))
- else {
- if(null != relationMap.get(rel.getRelationType + "_in_" + rel.getStartNodeObjectType)) {
- relMap.put(relationMap.get(rel.getRelationType + "_out_" + rel.getEndNodeObjectType).asInstanceOf[String], new util.ArrayList[util.Map[String, AnyRef]]() {})
- }
- }
- }
- relMap
- }
-
- def convertJsonProperties(entry: Map.Entry[String, AnyRef], jsonProps: scala.List[String]) = {
- if(jsonProps.contains(entry.getKey)) {
- try {mapper.readTree(entry.getValue.toString)}
- catch { case e: Exception => entry.getValue }
- }
- else entry.getValue
- }
-
- def populateRelationMaps(rel: Relation, direction: String): util.Map[String, AnyRef] = {
- if("out".equalsIgnoreCase(direction))
- new util.HashMap[String, Object]() {{
- put("identifier", rel.getEndNodeId.replace(".img", ""))
- put("name", rel.getEndNodeName)
- put("objectType", rel.getEndNodeObjectType.replace("Image", ""))
- put("relation", rel.getRelationType)
- put("description", rel.getEndNodeMetadata.get("description"))
- put("status", rel.getEndNodeMetadata.get("status"))
- }}
- else
- new util.HashMap[String, Object]() {{
- put("identifier", rel.getStartNodeId.replace(".img", ""))
- put("name", rel.getStartNodeName)
- put("objectType", rel.getStartNodeObjectType.replace("Image", ""))
- put("relation", rel.getRelationType)
- put("description", rel.getStartNodeMetadata.get("description"))
- put("status", rel.getStartNodeMetadata.get("status"))
- }}
- }
-}
\ No newline at end of file
diff --git a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/ScalaJsonUtils.scala b/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/ScalaJsonUtils.scala
deleted file mode 100644
index 9f5b283ec..000000000
--- a/learning-api/hierarchy-manager/src/main/scala/org/sunbird/utils/ScalaJsonUtils.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.sunbird.utils
-
-import com.fasterxml.jackson.core.`type`.TypeReference
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.scala.DefaultScalaModule
-
-object ScalaJsonUtils {
- @transient val mapper = new ObjectMapper()
- mapper.registerModule(DefaultScalaModule)
-
- @throws(classOf[Exception])
- def serialize(obj: AnyRef): String = {
- mapper.writeValueAsString(obj);
- }
-
- @throws(classOf[Exception])
- def deserialize[T: Class](value: String): T = {
- mapper.readValue(value, new TypeReference[T]{})
- }
-
-}
diff --git a/learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala b/learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala
deleted file mode 100644
index 1417edce0..000000000
--- a/learning-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.sunbird.managers
-
-import java.util
-
-import org.sunbird.common.dto.Request
-
-class TestHierarchy extends BaseSpec {
-
- private val script_1 = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};"
- private val script_2 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));"
- private val script_3 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414.img', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\"}]}');"
-
- override def beforeAll(): Unit = {
- super.beforeAll()
- graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Learner\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Live\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398314186_do_11283193441064550414_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Learner\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/do_11283193441064550414_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"test prad course cert\",status:\"Live\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550414\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row")
- executeCassandraQuery(script_1, script_2, script_3)
- }
-
-
- "addLeafNodesToHierarchy" should "addLeafNodesToHierarchy" in {
- val request = new Request()
- request.setContext(new util.HashMap[String, AnyRef]() {
- {
- put("objectType", "Content")
- put("graph_id", "domain")
- put("version", "1.0")
- put("schemaName", "collection")
- put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
- }
- })
-
- request.put("rootId", "do_11283193441064550414")
- request.put("unitId", "do_11283193463014195215")
- request.put("children", util.Arrays.asList("do_112831862871203840114"))
- val future = HierarchyManager.addLeafNodesToHierarchy(request)
- future.map(response => {
- assert(response.getResponseCode.code() == 200)
- assert(response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]]))
- assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215"))
- val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
- .one().getString("hierarchy")
- assert(hierarchy.contains("do_112831862871203840114"))
- })
- }
-
- "removeLeafNodesToHierarchy" should "removeLeafNodesToHierarchy" in {
- val request = new Request()
- request.setContext(new util.HashMap[String, AnyRef]() {
- {
- put("objectType", "Content")
- put("graph_id", "domain")
- put("version", "1.0")
- put("schemaName", "collection")
- put("channel", "b00bc992ef25f1a9a8d63291e20efc8d")
- }
- })
-
- request.put("rootId", "do_11283193441064550414")
- request.put("unitId", "do_11283193463014195215")
- request.put("children", util.Arrays.asList("do_112831862871203840114"))
- val future = HierarchyManager.addLeafNodesToHierarchy(request)
- future.map(response => {
- assert(response.getResponseCode.code() == 200)
- val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
- .one().getString("hierarchy")
- assert(hierarchy.contains("do_112831862871203840114"))
- val removeFuture = HierarchyManager.removeLeafNodesFromHierarchy(request)
- removeFuture.map(resp => {
- assert(resp.getResponseCode.code() == 200)
- val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'")
- .one().getString("hierarchy")
- assert(!hierarchy.contains("do_112831862871203840114"))
- })
- }).flatMap(f => f)
- }
-}
diff --git a/learning-api/orchestrator/pom.xml b/learning-api/orchestrator/pom.xml
deleted file mode 100644
index 9b6ce2214..000000000
--- a/learning-api/orchestrator/pom.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
- learning-api
- org.sunbird
- 1.0-SNAPSHOT
-
- 4.0.0
-
- orchestrator
-
-
-
- org.sunbird
- actor-core
- 1.0-SNAPSHOT
-
-
- org.sunbird
- hierarchy-manager
- 1.0-SNAPSHOT
-
-
- org.sunbird
- graph-engine_2.11
- 1.0-SNAPSHOT
- jar
-
-
-
-
\ No newline at end of file
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/actors/ContentActor.java b/learning-api/orchestrator/src/main/java/org/sunbird/actors/ContentActor.java
deleted file mode 100644
index 35c0c4c5a..000000000
--- a/learning-api/orchestrator/src/main/java/org/sunbird/actors/ContentActor.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.sunbird.actors;
-
-import akka.dispatch.Mapper;
-import org.apache.commons.lang3.StringUtils;
-import org.sunbird.actor.core.BaseActor;
-import org.sunbird.cache.util.RedisCacheUtil;
-import org.sunbird.common.ContentParams;
-import org.sunbird.common.dto.Request;
-import org.sunbird.common.dto.Response;
-import org.sunbird.common.dto.ResponseHandler;
-import org.sunbird.common.exception.ClientException;
-import org.sunbird.common.exception.ResponseCode;
-import org.sunbird.graph.dac.model.Node;
-import org.sunbird.graph.nodes.DataNode;
-import org.sunbird.utils.NodeUtils;
-import org.sunbird.utils.RequestUtils;
-import scala.concurrent.Future;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class ContentActor extends BaseActor {
-
- public Future onReceive(Request request) throws Throwable {
- String operation = request.getOperation();
- switch(operation) {
- case "createContent": return create(request);
- case "readContent": return read(request);
- case "updateContent": return update(request);
- default: return ERROR(operation);
- }
- }
-
- private Future create(Request request) throws Exception {
- populateDefaultersForCreation(request);
- RequestUtils.restrictProperties(request);
- return DataNode.create(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Response response = ResponseHandler.OK();
- response.put("node_id", node.getIdentifier());
- response.put("identifier", node.getIdentifier());
- response.put("versionKey", node.getMetadata().get("versionKey"));
- return response;
- }
- }, getContext().dispatcher());
- }
-
- private Future update(Request request) throws Exception {
- populateDefaultersForUpdation(request);
- if(StringUtils.isBlank((String)request.get("versionKey")))
- throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!");
- RequestUtils.restrictProperties(request);
- return DataNode.update(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Response response = ResponseHandler.OK();
- String identifier = node.getIdentifier().replace(".img","");
- response.put("node_id", identifier);
- response.put("identifier", identifier);
- response.put("versionKey", node.getMetadata().get("versionKey"));
- return response;
- }
- }, getContext().dispatcher());
- }
-
- private Future read(Request request) throws Exception {
- List fields = Arrays.stream(((String) request.get("fields")).split(","))
- .filter(field -> StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null")).collect(Collectors.toList());
- request.getRequest().put("fields", fields);
- return DataNode.read(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Map metadata = NodeUtils.serialize(node, fields, (String) request.getContext().get("schemaName"));
- Response response = ResponseHandler.OK();
- response.put("content", metadata);
- return response;
- }
- }, getContext().dispatcher());
- }
-
- private static void populateDefaultersForCreation(Request request) {
- setDefaultsBasedOnMimeType(request, ContentParams.create.name());
- setDefaultLicense(request);
- }
-
- private static void populateDefaultersForUpdation(Request request){
- if(request.getRequest().containsKey(ContentParams.body.name()))
- request.put(ContentParams.artifactUrl.name(), null);
- }
-
- private static void setDefaultLicense(Request request) {
- if(StringUtils.isEmpty((String)request.getRequest().get("license"))){
- String defaultLicense = RedisCacheUtil.getString("channel_" + (String)request.getRequest().get("channel") + "_license");
- if(StringUtils.isNotEmpty(defaultLicense))
- request.getRequest().put("license", defaultLicense);
- else
- System.out.println("Default License is not available for channel: " + (String)request.getRequest().get("channel"));
- }
- }
-
- private static void setDefaultsBasedOnMimeType(Request request, String operation) {
-
- String mimeType = (String) request.get(ContentParams.mimeType.name());
- if (StringUtils.isNotBlank(mimeType) && operation.equalsIgnoreCase(ContentParams.create.name())) {
- if (StringUtils.equalsIgnoreCase("application/vnd.ekstep.plugin-archive", mimeType)) {
- String code = (String) request.get(ContentParams.code.name());
- if (null == code || StringUtils.isBlank(code))
- throw new ClientException("ERR_PLUGIN_CODE_REQUIRED", "Unique code is mandatory for plugins");
- request.put(ContentParams.identifier.name(), request.get(ContentParams.code.name()));
- } else {
- request.put(ContentParams.osId.name(), "org.ekstep.quiz.app");
- }
-
- if (mimeType.endsWith("archive") || mimeType.endsWith("vnd.ekstep.content-collection")
- || mimeType.endsWith("epub"))
- request.put(ContentParams.contentEncoding.name(), ContentParams.gzip.name());
- else
- request.put(ContentParams.contentEncoding.name(), ContentParams.identity.name());
-
- if (mimeType.endsWith("youtube") || mimeType.endsWith("x-url"))
- request.put(ContentParams.contentDisposition.name(), ContentParams.online.name());
- else
- request.put(ContentParams.contentDisposition.name(), ContentParams.inline.name());
- }
- }
-
-}
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/actors/HealthActor.java b/learning-api/orchestrator/src/main/java/org/sunbird/actors/HealthActor.java
deleted file mode 100644
index e268fcbbf..000000000
--- a/learning-api/orchestrator/src/main/java/org/sunbird/actors/HealthActor.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.sunbird.actors;
-
-import akka.dispatch.Futures;
-import org.sunbird.actor.core.BaseActor;
-import org.sunbird.common.dto.Request;
-import org.sunbird.common.dto.Response;
-import org.sunbird.common.dto.ResponseHandler;
-import scala.concurrent.Future;
-
-public class HealthActor extends BaseActor {
-
- @Override
- public Future onReceive(Request request) throws Throwable {
- Response result = ResponseHandler.OK();
- result.put("healthy", true);
- return Futures.successful(result);
- }
-}
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/actors/LicenseActor.java b/learning-api/orchestrator/src/main/java/org/sunbird/actors/LicenseActor.java
deleted file mode 100644
index 05a3e4b80..000000000
--- a/learning-api/orchestrator/src/main/java/org/sunbird/actors/LicenseActor.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.sunbird.actors;
-
-import akka.dispatch.Mapper;
-import org.apache.commons.lang3.StringUtils;
-import org.sunbird.actor.core.BaseActor;
-
-import org.sunbird.common.Slug;
-
-import org.sunbird.common.dto.Request;
-import org.sunbird.common.dto.Response;
-import org.sunbird.common.dto.ResponseHandler;
-import org.sunbird.common.exception.ClientException;
-import org.sunbird.common.exception.ResponseCode;
-import org.sunbird.graph.dac.model.Node;
-import org.sunbird.graph.nodes.DataNode;
-import org.sunbird.utils.NodeUtils;
-import scala.concurrent.Future;
-import org.sunbird.utils.LicenseOperations;
-import org.sunbird.utils.RequestUtils;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class LicenseActor extends BaseActor {
-
- public Future onReceive(Request request) throws Throwable {
- String operation = request.getOperation();
- if (LicenseOperations.createLicense.name().equals(operation)) {
- return create(request);
- } else if (LicenseOperations.readLicense.name().equals(operation)) {
- return read(request);
- } else if (LicenseOperations.updateLicense.name().equals(operation)) {
- return update(request);
- } else if (LicenseOperations.retireLicense.name().equals(operation)) {
- return retire(request);
- } else {
- return ERROR(operation);
-
- }
- }
-
- private Future create(Request request) throws Exception {
- RequestUtils.restrictProperties(request);
- if (request.getRequest().containsKey("identifier")) {
- throw new ClientException("ERR_NAME_SET_AS_IDENTIFIER", "name will be set as identifier");
- }
- if (request.getRequest().containsKey("name")) {
- request.getRequest().put("identifier", Slug.makeSlug((String) request.getRequest().get("name")));
- }
- return DataNode.create(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Response response = ResponseHandler.OK();
- response.put("node_id", node.getIdentifier());
- return response;
- }
- }, getContext().dispatcher());
- }
-
- private Future read(Request request) throws Exception {
- List fields = Arrays.stream(((String) request.get("fields")).split(","))
- .filter(field -> StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null")).collect(Collectors.toList());
- request.getRequest().put("fields", fields);
- return DataNode.read(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- if(NodeUtils.isRetired(node))
- return ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "License not found with identifier: " + node.getIdentifier());
- Map metadata = NodeUtils.serialize(node, fields, (String) request.getContext().get("schemaName"));
- Response response = ResponseHandler.OK();
- response.put("license", metadata);
- return response;
- }
- }, getContext().dispatcher());
- }
-
- private Future update(Request request) throws Exception {
- RequestUtils.restrictProperties(request);
- request.getRequest().put("status", "Live");
- return DataNode.update(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Response response = ResponseHandler.OK();
- response.put("node_id", node.getIdentifier());
- return response;
- }
- }, getContext().dispatcher());
- }
- private Future retire(Request request) throws Exception {
- request.getRequest().put("status", "Retired");
- return DataNode.update(request, getContext().dispatcher())
- .map(new Mapper() {
- @Override
- public Response apply(Node node) {
- Response response = ResponseHandler.OK();
- response.put("node_id", node.getIdentifier());
- return response;
- }
- }, getContext().dispatcher());
- }
-}
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/utils/LicenseOperations.java b/learning-api/orchestrator/src/main/java/org/sunbird/utils/LicenseOperations.java
deleted file mode 100644
index c6f792b32..000000000
--- a/learning-api/orchestrator/src/main/java/org/sunbird/utils/LicenseOperations.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.sunbird.utils;
-
-public enum LicenseOperations {
- createLicense, readLicense, updateLicense, retireLicense
-}
diff --git a/learning-api/orchestrator/src/main/java/org/sunbird/utils/NodeUtils.java b/learning-api/orchestrator/src/main/java/org/sunbird/utils/NodeUtils.java
deleted file mode 100644
index f493bc032..000000000
--- a/learning-api/orchestrator/src/main/java/org/sunbird/utils/NodeUtils.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.sunbird.utils;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.sunbird.common.Platform;
-import org.sunbird.graph.dac.model.Node;
-import org.sunbird.graph.dac.model.Relation;
-import org.sunbird.graph.schema.DefinitionNode;
-import scala.collection.JavaConversions;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-public class NodeUtils {
- private static final ObjectMapper mapper = new ObjectMapper();
-
- /**
- * This method will convert a Node to map
- * @param node
- * @param fields
- * @return
- */
- public static Map serialize(Node node, List fields, String schemaName) {
- Map metadataMap = new HashMap<>();
- metadataMap.putAll(node.getMetadata());
- metadataMap.put("languageCode",getLanguageCodes(node));
- if (CollectionUtils.isNotEmpty(fields))
- filterOutFields(metadataMap, fields);
- metadataMap.put("identifier", node.getIdentifier().replace(".img",""));
- List jsonProps = JavaConversions.seqAsJavaList(DefinitionNode.fetchJsonProps(node.getGraphId(), "1.0", schemaName));
- Map updatedMetadataMap = metadataMap.entrySet().stream().filter(entry -> null != entry.getValue()).collect(Collectors.toMap(entry -> handleKeyNames(entry, fields), entry -> convertJsonProperties(entry, jsonProps)));
- Map definitionMap = JavaConversions.mapAsJavaMap(DefinitionNode.getRelationDefinitionMap(node.getGraphId(), "1.0", schemaName));
- if (CollectionUtils.isEmpty(fields) || definitionMap.keySet().stream().anyMatch(key -> fields.contains(key))) {
- getRelationMap(node, updatedMetadataMap, definitionMap);
- }
- return updatedMetadataMap;
- }
-
- private static List getLanguageCodes(Node node) {
- List languages = new ArrayList<>();
- Object language = node.getMetadata().get("language");
- if (language instanceof String[] )
- languages.addAll(Arrays.asList( (String[]) language));
- else if(language instanceof List)
- languages.addAll((List) language);
- return languages.stream().map(lang -> Platform.config.hasPath("languageCode." + lang.toLowerCase()) ? Platform.config.getString("languageCode." + lang.toLowerCase()) : "").collect(Collectors.toList());
- }
-
- private static void filterOutFields(Map inputMetadata, List fields) {
- inputMetadata.keySet().retainAll(fields);
- }
-
- private static Object convertJsonProperties(Map.Entry entry, List jsonProps) {
- if (jsonProps.contains(entry.getKey()))
- try {
- return mapper.readTree(entry.getValue().toString());
- } catch (Exception e) {
- return entry.getValue();
- }
- else
- return entry.getValue();
- }
-
- private static String handleKeyNames(Map.Entry entry, List fields) {
- if (CollectionUtils.isEmpty(fields))
- return entry.getKey().substring(0, 1) + entry.getKey().substring(1);
- else
- return entry.getKey();
- }
-
- private static void getRelationMap(Node node, Map metadata, Map relationMap) {
- List inRelations = CollectionUtils.isEmpty(node.getInRelations()) ? new ArrayList<>(): node.getInRelations();
- List outRelations = CollectionUtils.isEmpty(node.getOutRelations()) ? new ArrayList<>(): node.getOutRelations();
- Map>> relMap = new HashMap<>();
- for (Relation rel : inRelations) {
- if (relMap.containsKey(relationMap.get(rel.getRelationType() + "_in_" + rel.getStartNodeObjectType()))) {
- relMap.get(relationMap.get(rel.getRelationType() + "_in_" + rel.getStartNodeObjectType())).add(populateRelationMaps(rel, "in"));
- } else {
- String relKey = (String) relationMap.get(rel.getRelationType() + "_in_" + rel.getStartNodeObjectType());
- if (StringUtils.isNotBlank(relKey)) {
- relMap.put(relKey,
- new ArrayList