diff --git a/app/actors/ActorsModule.scala b/app/actors/ActorsModule.scala index 65aeeee..5be9530 100644 --- a/app/actors/ActorsModule.scala +++ b/app/actors/ActorsModule.scala @@ -27,6 +27,5 @@ import play.api.libs.concurrent.AkkaGuiceSupport class ActorsModule extends AbstractModule with AkkaGuiceSupport { def configure = { - bindActor[DocumentsActor]("actors-documents") } } diff --git a/app/actors/DocumentsActor.scala b/app/actors/DocumentsActor.scala deleted file mode 100644 index b5d2ea2..0000000 --- a/app/actors/DocumentsActor.scala +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package actors - -import akka.actor._ -import java.util.UUID.randomUUID -import javax.inject._ -import play.api.libs.json._ -import scala.collection.immutable -import scala.util.{ Success, Failure } - -// ours -import org.xalgorithms.storage.bson.Find -import org.xalgorithms.storage.data.{ MongoActions } - -// local -import services.InjectableMongo - -import scala.concurrent.ExecutionContext.Implicits.global - -object DocumentsActor { - case class StoreSubmission(doc: JsObject, effective_ctxs: Option[Seq[Map[String, String]]]) - case class StoreEffectiveVerification(doc: JsObject, effective_ctxs: Option[Seq[Map[String, String]]]) - case class StoreApplicableVerification(doc: JsObject, rule_id: String) - case class StoreExecution(rule_id: String, opt_ctx: Option[JsObject]) -} - -class DocumentsActor @Inject() (mongo: InjectableMongo, publish: services.Publish) extends Actor with ActorLogging { - import DocumentsActor._ - - def store_document_and_publish( - doc: JsObject, - opt_effective_ctxs: Option[Seq[Map[String, String]]], - fn: (String, Option[Seq[Map[String, String]]]) => GlobalMessages.GlobalMessage - ) = { - val them = sender() - mongo.store(new MongoActions.StoreDocument(doc)).onComplete { - case Success(public_id) => { - log.debug(s"stored (public_id=${public_id})") - publish.publish_global(fn(public_id, opt_effective_ctxs)) - them ! public_id - } - case Failure(th) => { - log.error(s"failed store") - } - } - } - - def receive = { - case StoreSubmission(doc, opt_effective_ctxs) => { - val fn = (public_id: String, effective_ctxs: Option[Seq[Map[String, String]]]) => { - GlobalMessages.SubmissionAdded(public_id, opt_effective_ctxs) - } - - store_document_and_publish(doc, opt_effective_ctxs, fn) - } - - case StoreEffectiveVerification(doc, opt_effective_ctxs) => { - val fn = (public_id: String, effective_ctxs: Option[Seq[Map[String, String]]]) => { - GlobalMessages.EffectiveVerificationAdded(public_id, opt_effective_ctxs) - } - - store_document_and_publish(doc, opt_effective_ctxs, fn) - } - - case StoreApplicableVerification(doc, rule_id) => { - val fn = (public_id: String, effective_ctxs: Option[Seq[Map[String, String]]]) => { - GlobalMessages.ApplicableVerificationAdded(public_id, rule_id) - } - - store_document_and_publish(doc, None, fn) - } - - case StoreExecution(rule_id, opt_ctx) => { - val them = sender() - log.debug(s"storing execution (rule_id=${rule_id})") - mongo.store(new MongoActions.StoreExecution(rule_id, opt_ctx.getOrElse(Json.obj()))).onComplete { - case Success(request_id) => { - log.debug(s"stored execution (request_id=${request_id})") - publish.publish_global(GlobalMessages.ExecutionAdded(request_id)) - them ! request_id - } - - case Failure(th) => { - log.error("failed store") - println(th) - } - } - } - } -} diff --git a/app/actors/GlobalMessages.scala b/app/actors/GlobalMessages.scala index 0a7f8df..1480dd2 100644 --- a/app/actors/GlobalMessages.scala +++ b/app/actors/GlobalMessages.scala @@ -22,14 +22,12 @@ // . package actors +import play.api.libs.json._ + object GlobalMessages { abstract class GlobalMessage - case class SubmissionAdded( - id: String, effective_ctx: Option[Seq[Map[String, String]]] - ) extends GlobalMessage - case class EffectiveVerificationAdded( - id: String, effective_ctx: Option[Seq[Map[String, String]]] - ) extends GlobalMessage - case class ApplicableVerificationAdded(doc_id: String, rule_id: String) extends GlobalMessage - case class ExecutionAdded(id: String) extends GlobalMessage + case class Execute(rule_id: String, req_id: String, opt_doc: Option[JsObject]) extends GlobalMessage + case class Submit(req_id: String, effective_props: Map[String, String], doc: JsObject) extends GlobalMessage + case class VerifyEffective(req_id: String, effective_props: Map[String, String], doc: JsObject) extends GlobalMessage + case class VerifyApplicable(req_id: String, rule_id: String, doc: JsObject) extends GlobalMessage } diff --git a/app/actors/Messages.scala b/app/actors/Messages.scala deleted file mode 100644 index 9c60795..0000000 --- a/app/actors/Messages.scala +++ /dev/null @@ -1,44 +0,0 @@ -package actors - -import play.api.libs.json._ -import play.api.libs.functional.syntax._ - -object Triggers { - abstract class Trigger - case class TriggerById(id: String) extends Trigger - case class TriggerDocument(doc_id: String, effective_ctx: Map[String, String] = null) extends Trigger - case class TriggerApplicable(doc_id: String, rule_id: String) extends Trigger -} - -object Implicits { - import Triggers._ - - implicit val trigger_writes = new Writes[Trigger] { - def writes(tr: Trigger) = tr match { - case TriggerById(id) => Json.obj( - "context" -> Map("task" -> "triggers", "action" -> "trigger_by_id"), - "args" -> Map("id" -> id) - ) - - case TriggerDocument(doc_id, effective_ctx) => Json.obj( - "context" -> Map("task" -> "triggers", "action" -> "trigger_document"), - "args" -> Json.obj( - "document_id" -> doc_id, - "effective_context" -> effective_ctx - ) - ) - - case TriggerApplicable(doc_id, rule_id) => Json.obj( - "context" -> Map("task" -> "triggers", "action" -> "trigger_applicable"), - "args" -> Json.obj( - "document_id" -> doc_id, - "rule_id" -> rule_id - ) - ) - } - } -} - -object Actions { - case class InvokeTrigger(topic: String, trigger: Triggers.Trigger) -} diff --git a/app/actors/MessagesActor.scala b/app/actors/MessagesActor.scala index c19bbb7..b22f8ff 100644 --- a/app/actors/MessagesActor.scala +++ b/app/actors/MessagesActor.scala @@ -41,10 +41,6 @@ object MessagesActor { class MessagesActor extends Actor with ActorLogging { implicit val materializer = ActorMaterializer() - import Triggers._ - import Actions.InvokeTrigger - import Implicits.trigger_writes - private val broker = Properties.envOrElse("KAFKA_BROKER", "kafka:9092") log.info(s"creating kafka settings (broker=${broker})") @@ -52,53 +48,67 @@ class MessagesActor extends Actor with ActorLogging { context.system, new StringSerializer, new StringSerializer ).withBootstrapServers(broker) - private val _source = Source.queue[InvokeTrigger](5, OverflowStrategy.backpressure) - private val _flow_json = Flow[InvokeTrigger].map { o => - (o.topic, Json.toJson(o.trigger)) - } + private val _source = Source.queue[(String, JsValue)](5, OverflowStrategy.backpressure) private val _flow_record = Flow[(String, JsValue)].map { case (topic, payload) => new ProducerRecord[String, String](topic, payload.toString) } log.info("setting up stream") - val _triggers = _source.via(_flow_json).via(_flow_record).to(Producer.plainSink(settings)).run() - - def trigger_document_on_topic( - topic: String, - doc_id: String, - opt_effective_ctxs: Option[Seq[Map[String, String]]] = None - ) = { - opt_effective_ctxs match { - case Some(effective_ctxs) => { - effective_ctxs.foreach { effective_ctx => - send(topic, TriggerDocument(doc_id, effective_ctx)) - } - } - case None => log.debug("no context supplied") - } - } + val _triggers = _source.via(_flow_record).to(Producer.plainSink(settings)).run() def receive = { - case GlobalMessages.SubmissionAdded(doc_id, opt_effective_ctxs) => { - trigger_document_on_topic("il.compute.execute", doc_id, opt_effective_ctxs) + case GlobalMessages.Execute(rule_id, req_id, opt_doc) => { + log.debug(s"execute (rule_id=${rule_id}; req_id=${req_id})") + val args = Json.obj( + "rule_id" -> rule_id, + "request_id" -> req_id + ) ++ (opt_doc match { + case Some(doc) => Json.obj("context" -> doc) + case None => Json.obj() + }) + + val o = Json.obj( + "context" -> Map("task" -> "triggers", "action" -> "execute_rule"), + "args" -> args + ) + _triggers.offer(("il.compute.execute", o)) } - case GlobalMessages.EffectiveVerificationAdded(doc_id, opt_effective_ctxs) => { - trigger_document_on_topic("il.verify.effective", doc_id, opt_effective_ctxs) + case GlobalMessages.Submit(req_id, effective_props, doc) => { + val o = Json.obj( + "context" -> Map("task" -> "triggers", "action" -> "submit_document"), + "args" -> Json.obj( + "request_id" -> req_id, + "effective_properties" -> effective_props, + "document" -> doc + ) + ) + _triggers.offer(("il.compute.documents", o)) } - case GlobalMessages.ApplicableVerificationAdded(doc_id, rule_id) => { - send("il.verify.applicable", TriggerApplicable(doc_id, rule_id)) + case GlobalMessages.VerifyEffective(req_id, effective_props, doc) => { + val o = Json.obj( + "context" -> Map("task" -> "triggers", "action" -> "verify_effective"), + "args" -> Json.obj( + "request_id" -> req_id, + "effective_properties" -> effective_props, + "document" -> doc + ) + ) + _triggers.offer(("il.verify.effective", o)) } - case GlobalMessages.ExecutionAdded(id) => { - send("il.verify.rule_execution", TriggerById(id)) + case GlobalMessages.VerifyApplicable(req_id, rule_id, doc) => { + val o = Json.obj( + "context" -> Map("task" -> "triggers", "action" -> "verify_applicable"), + "args" -> Json.obj( + "request_id" -> req_id, + "rule_id" -> rule_id, + "document" -> doc + ) + ) + log.debug("here") + _triggers.offer(("il.verify.applicable", o)) } } - - private def send(topic: String, trigger: Trigger) = { - log.debug(s"> sending message (topic=${topic})") - _triggers.offer(InvokeTrigger(topic, trigger)) - log.debug(s"< sent message (topic=${topic})") - } } diff --git a/app/controllers/ActionsController.scala b/app/controllers/ActionsController.scala index ab90f4f..51ea2f2 100644 --- a/app/controllers/ActionsController.scala +++ b/app/controllers/ActionsController.scala @@ -36,13 +36,11 @@ import scala.util.{ Success, Failure } import ExecutionContext.Implicits.global -import actors.DocumentsActor - case class AppAction(name: String, args: Map[String, String], document: Option[JsObject]) @Singleton class ActionsController @Inject()( - @Named("actors-documents") actor_docs: ActorRef, + system: ActorSystem, publish: services.Publish, cc: ControllerComponents ) extends AbstractController(cc) { @@ -55,7 +53,7 @@ class ActionsController @Inject()( // for ask implicit val timeout: Timeout = 5.seconds - val effective_ctx_keys = Seq( + val effective_keys = Seq( "key", "country", "region", @@ -66,8 +64,16 @@ class ActionsController @Inject()( // NOTE: a version of this exists in // storage/src/main/scala/org/xalgorithms/storage/data/Mongo.scala // the storage lib should provide a factored make_rule_id - def make_rule_id(ns: String, name: String, version: String): String = { - play.api.libs.Codecs.sha1(s"R(${ns}:${name}:${version})") + def make_rule_id_from(args: Map[String, String]): String = { + val ns = args.getOrElse("namespace", "") + val name = args.getOrElse("name", "") + val ver = args.getOrElse("version", "") + + val rule_id = play.api.libs.Codecs.sha1(s"R(${ns}:${name}:${ver})") + + Logger.debug(s"generating rule_id (ns=${ns}; name=${name}; ver=${ver}; rule_id=${rule_id})") + + rule_id } def validate_json[A : Reads] = parse.json.validate( @@ -75,97 +81,125 @@ class ActionsController @Inject()( ) def apply_execute(args: Map[String, String], opt_doc: Option[JsObject]): Future[Result] = { - val ns = args.getOrElse("namespace", null) - val name = args.getOrElse("name", null) - val ver = args.getOrElse("version", null) - val rule_id = make_rule_id(ns, name, ver) + val rule_id = make_rule_id_from(args) + val req_id = java.util.UUID.randomUUID.toString + val ref = system.actorOf(actors.MessagesActor.props) - Logger.debug(s"executing (ns=${ns}; name=${name}; ver=${ver}; rule_id=${rule_id})") - - (actor_docs ? DocumentsActor.StoreExecution(rule_id, opt_doc)).mapTo[String].map { req_id => - Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) - } + Logger.debug(s"executing (rule_id=${rule_id}; req_id=${req_id})") + ref ! actors.GlobalMessages.Execute(rule_id, req_id, opt_doc) + Future.successful(Ok(Json.obj("status" -> "ok", "request_id" -> req_id))) } def apply_submit(args: Map[String, String], opt_doc: Option[JsObject]): Future[Result] = { - val opt_content = opt_doc.flatMap { doc => - (doc \ "content").asOpt[JsObject] - } - - val opt_effective_ctxs = opt_doc.flatMap { doc => - (doc \ "effective_contexts").asOpt[JsArray].map { os => - os.value.map { o => - effective_ctx_keys.foldLeft(Map[String, String]()) { (m, k) => - (o \ k).asOpt[String] match { - case Some(v) => m ++ Map(k -> v) - case None => m - } - } - } + // each submission is a document in a SINGLE effectiveness, therefore, we + // keep the effective keys in the args + opt_doc match { + case Some(doc) => { + val req_id = java.util.UUID.randomUUID.toString + val effective_props = args.filterKeys(effective_keys.contains(_)) + val ref = system.actorOf(actors.MessagesActor.props) + + Logger.debug(s"submitting document (props=${effective_props})") + ref ! actors.GlobalMessages.Submit(req_id, effective_props, doc) + Future.successful(Ok(Json.obj("status" -> "ok", "request_id" -> req_id))) } - } - opt_content match { - case Some(content) => { - (actor_docs ? DocumentsActor.StoreSubmission(content, opt_effective_ctxs)).mapTo[String].map { req_id => - Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) - } - } - case None => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "document_required"))) + case None => Future.successful(BadRequest(Json.obj("status" -> "fail_no_document"))) } } - def apply_verify(args: Map[String, String], opt_doc: Option[JsObject]): Future[Result] = { - val opt_content = opt_doc.flatMap { doc => - (doc \ "content").asOpt[JsObject] - } + def apply_verify_effective(args: Map[String, String], doc: JsObject): Future[Result] = { + val req_id = java.util.UUID.randomUUID.toString + val effective_props = args.filterKeys(effective_keys.contains(_)) + val ref = system.actorOf(actors.MessagesActor.props) - val opt_effective_ctxs = opt_doc.flatMap { doc => - // TODO: parse doc/sections - (doc \ "effective_contexts").asOpt[JsArray].map { os => - os.value.map { o => - effective_ctx_keys.foldLeft(Map[String, String]()) { (m, k) => - (o \ k).asOpt[String] match { - case Some(v) => m ++ Map(k -> v) - case None => m - } - } - } + ref ! actors.GlobalMessages.VerifyEffective(req_id, effective_props, doc) + + Future.successful(Ok(Json.obj("status" -> "ok", "request_id" -> req_id))) + } + + def apply_verify_applicable(args: Map[String, String], doc: JsObject): Future[Result] = { + args.get("rule_id") match { + case Some(rule_id) => { + val req_id = java.util.UUID.randomUUID.toString + val ref = system.actorOf(actors.MessagesActor.props) + + ref ! actors.GlobalMessages.VerifyApplicable(req_id, rule_id, doc) + + Future.successful(Ok(Json.obj("status" -> "ok", "request_id" -> req_id))) } + + case None => Future.successful(BadRequest(Json.obj("status" -> "fail_rule_id_required"))) } - args("what") match { - case "effective" => { - opt_content match { - case Some(content) => { - val m = DocumentsActor.StoreEffectiveVerification(content, opt_effective_ctxs) - (actor_docs ? m).mapTo[String].map { req_id => - Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) - } - } - - case None => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "no_content"))) - } - } + } - case "applicable" => { - opt_content match { - case Some(content) => { - val rule_id = args("rule_id") - val m = DocumentsActor.StoreApplicableVerification(content, rule_id) - (actor_docs ? m).mapTo[String].map { req_id => - Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) - } - } - - case None => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "no_content"))) + def apply_verify(args: Map[String, String], opt_doc: Option[JsObject]): Future[Result] = { + opt_doc match { + case Some(doc) => args.get("what") match { + case Some(what) => what match { + case "effective" => apply_verify_effective(args, doc) + case "applicable" => apply_verify_applicable(args, doc) + case _ => Future.successful(BadRequest(Json.obj("status" -> "fail_unknown_what", "what" -> what))) } + case None => Future.successful(BadRequest(Json.obj("status" -> "fail_no_what"))) } - case _ => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "unknown_verify_what"))) + case None => Future.successful(BadRequest(Json.obj("status" -> "fail_no_document"))) } } + // def apply_verify(args: Map[String, String], opt_doc: Option[JsObject]): Future[Result] = { + // val opt_content = opt_doc.flatMap { doc => + // (doc \ "content").asOpt[JsObject] + // } + + // val opt_effective_ctxs = opt_doc.flatMap { doc => + // // TODO: parse doc/sections + // (doc \ "effective_contexts").asOpt[JsArray].map { os => + // os.value.map { o => + // effective_ctx_keys.foldLeft(Map[String, String]()) { (m, k) => + // (o \ k).asOpt[String] match { + // case Some(v) => m ++ Map(k -> v) + // case None => m + // } + // } + // } + // } + // } + + // args("what") match { + // case "effective" => { + // opt_content match { + // case Some(content) => { + // val m = DocumentsActor.StoreEffectiveVerification(content, opt_effective_ctxs) + // (actor_docs ? m).mapTo[String].map { req_id => + // Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) + // } + // } + + // case None => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "no_content"))) + // } + // } + + // case "applicable" => { + // opt_content match { + // case Some(content) => { + // val rule_id = args("rule_id") + // val m = DocumentsActor.StoreApplicableVerification(content, rule_id) + // (actor_docs ? m).mapTo[String].map { req_id => + // Ok(Json.obj("status" -> "ok", "request_id" -> req_id)) + // } + // } + + // case None => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "no_content"))) + // } + // } + + // case _ => Future.successful(Ok(Json.obj("status" -> "fail", "reason" -> "unknown_verify_what"))) + // } + // } + private val actions = Map( "execute" -> (apply_execute _), "submit" -> (apply_submit _), diff --git a/app/models/Document.scala b/app/models/Document.scala deleted file mode 100644 index 9947f60..0000000 --- a/app/models/Document.scala +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import collection.JavaConverters._ -import collection.immutable -import org.bson._ -import org.joda.time.DateTime - -object Document { - def maybe_find_text(doc: BsonDocument, k: String): Option[String] = { - maybe_find_value(doc, k) match { - case Some(v) => Option(convert_to_string(v)) - case None => None - } - } - - def maybe_find_many_text(doc: BsonDocument, ks: Seq[String]): Seq[String] = { - maybe_find_many_values(doc, ks).foldLeft(Seq[String]()) { (seq, v) => - convert_to_string(v) match { - case null => seq - case (s: String) => seq :+ s - } - } - } - - def maybe_find_first_text(doc: BsonDocument, ks: Seq[String]): Option[String] = { - val vals = maybe_find_many_text(doc, ks) - vals.size match { - case 0 => None - case _ => Some(vals.head) - } - } - - def maybe_find_document(doc: BsonDocument, k: String): Option[BsonDocument] = { - maybe_find_value(doc, k) match { - case Some(v) => Option(convert_to_document(v)) - case None => None - } - } - - def maybe_find_many_document(doc: BsonDocument, ks: Seq[String]): Seq[BsonDocument] = { - maybe_find_many_values(doc, ks).foldLeft(Seq[BsonDocument]()) { (seq, v) => - convert_to_document(v) match { - case null => seq - case doc => seq :+ doc - } - } - } - - def maybe_find_first_document(doc: BsonDocument, ks: Seq[String]): Option[BsonDocument] = { - val vals = maybe_find_many_document(doc, ks) - vals.size match { - case 0 => None - case _ => Some(vals.head) - } - } - - def maybe_find_date_time(doc: BsonDocument, k: String): Option[DateTime] = { - maybe_find_value(doc, k) match { - case Some(v) => Option(convert_to_date_time(v)) - case None => None - } - } - - def maybe_find_many_values(doc: BsonDocument, ks: Seq[String]): Seq[BsonValue] = { - ks.foldLeft(Seq[BsonValue]()) { (seq, k) => - maybe_find_value(doc, k) match { - case Some(v) => seq :+ v - case None => seq - } - } - } - - def maybe_find_first_values(doc: BsonDocument, ks: Seq[String]): Option[BsonValue] = { - val ms = maybe_find_many_values(doc, ks) - ms.size match { - case 0 => None - case _ => Some(ms.head) - } - } - - def maybe_find_value(doc: BsonDocument, k: String): Option[BsonValue] = { - maybe_find_value(doc, k.split('.')) - } - - def maybe_find_value(doc: BsonDocument, ks: Seq[String]): Option[BsonValue] = ks.size match { - case 1 => Option(doc.get(ks.head, null)) - case len if len > 1 => { - maybe_find_value(Option(doc.getDocument(ks.head, null)), ks.tail) - } - case _ => None - } - - def maybe_find_value(opt_doc: Option[BsonDocument], ks: Seq[String]): Option[BsonValue] = opt_doc match { - case Some(doc) => maybe_find_value(doc, ks) - case None => None - } - - def convert_to_document(v: BsonValue): BsonDocument = v match { - case (dv: BsonDocument) => dv - case _ => null - } - - def convert_to_date_time(v: BsonValue): DateTime = v match { - case (nv: BsonString) => { - try { - new DateTime(nv.getValue()) - } catch { - case _: Throwable => null - } - } - - case (nv: BsonTimestamp) => new DateTime(nv.getTime().toLong * 1000) - case (nv: BsonDateTime) => new DateTime(nv.getValue()) - case _ => null - } - - def convert_to_string(v: BsonValue): String = v match { - // case (nv: BsonBoolean) => nv.toString() - // case BsonType.DATE_TIME => v.asDateTime().() - // case BsonType.DOUBLE => v.asDouble().toString() - // case BsonType.INT64 => v.asInt64().toString() - // case BsonType.INT32 => v.asInt32().toString() - case (nv: BsonString) => nv.getValue() - // case BsonType.TIMESTAMP => match_value(v.asTimestamp()) - case _ => null - } -} diff --git a/app/models/DocumentEnvelope.scala b/app/models/DocumentEnvelope.scala deleted file mode 100644 index d5631b2..0000000 --- a/app/models/DocumentEnvelope.scala +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import org.joda.time.DateTime -import org.mongodb.scala.bson.{ BsonDocument, BsonString } - -import services.{ City, Country, Subdivision, NormalizeCountry, NormalizeSubdivision, TimeZones } - -class DocumentEnvelope(id: String, doc: BsonDocument, timezones: TimeZones = new TimeZones() ) { - private val _party_keys = Seq("supplier", "customer", "payee", "buyer", "seller", "tax") - private val _address_keys = Seq("address", "location.address") - - def rows: Seq[Envelope] = { - // find the envelope in the document - Document.maybe_find_document(doc, "content.envelope") match { - case Some(env) => { - val issued = Document.maybe_find_date_time(env, "issued").getOrElse(new DateTime()) - val parties = _party_keys.foldLeft(Map[String, Jurisdiction]()) { (m, k) => - maybe_extract_jurisdiction(Document.maybe_find_document(env, k)) match { - case Some(jurisdiction) => { - m ++ Map(k -> jurisdiction) - } - case None => m - } - } - - parties.map { case (name, jurisdiction) => - val full_code = s"${jurisdiction.country.code2}-${jurisdiction.region.code}" - Envelope(id, name, jurisdiction.country.code2, full_code, jurisdiction.timezone, issued) - }.toSeq - } - case None => Seq() - } - } - - private def maybe_extract_jurisdiction( - opt_party_doc: Option[BsonDocument] - ): Option[Jurisdiction] = { - opt_party_doc.map( - Document.maybe_find_first_document(_, _address_keys).map( - extract_jurisdiction_from_address(_) - ) - ).flatten - } - - private def extract_jurisdiction_from_address(doc: BsonDocument): Jurisdiction = { - val country = NormalizeCountry(Country( - Document.maybe_find_text(doc, "country.name").getOrElse(null), - Document.maybe_find_text(doc, "country.code.value").getOrElse(null) - )) - val region = NormalizeSubdivision(country.code2, Subdivision( - Document.maybe_find_text(doc, "subentity.name").getOrElse(null), - Document.maybe_find_text(doc, "subentity.code.value").getOrElse(null) - )) - - val city = Document.maybe_find_text(doc, "city").map(City(_)) - val tz = timezones.lookup(Option(country), Option(region), city).getOrElse(null) - - Jurisdiction(country, region, tz) - } - -} - -case class Jurisdiction(country: Country, region: Subdivision, timezone: String) diff --git a/app/models/Envelope.scala b/app/models/Envelope.scala deleted file mode 100644 index 2ad67b5..0000000 --- a/app/models/Envelope.scala +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import org.joda.time.DateTime - -case class Envelope( - document_id: String, - party: String, - country: String, - region: String, - timezone: String, - issued: DateTime -) diff --git a/app/models/EnvelopesTable.scala b/app/models/EnvelopesTable.scala deleted file mode 100644 index f691d34..0000000 --- a/app/models/EnvelopesTable.scala +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import com.outworkers.phantom.dsl._ -import org.joda.time.DateTime -import scala.concurrent.Future - -abstract class EnvelopesTable extends Table[EnvelopesTable, Envelope] { - // note to self: you MUST have PartitionKey or this fails to compile - object document_id extends StringColumn with PartitionKey - object party extends StringColumn - object country extends StringColumn - object region extends StringColumn - object timezone extends StringColumn - object issued extends DateTimeColumn - - override lazy val tableName = "envelopes" - - def find(document_id: String): Future[Option[Envelope]] = { - select.where(_.document_id eqs document_id).one() - } -} diff --git a/app/models/InterlibrDatabase.scala b/app/models/InterlibrDatabase.scala deleted file mode 100644 index b45f1c5..0000000 --- a/app/models/InterlibrDatabase.scala +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import com.outworkers.phantom.connectors -import com.outworkers.phantom.connectors.CassandraConnection -import com.outworkers.phantom.dsl._ -import scala.concurrent.Future - -class InterlibrDatabase( - override val connector: CassandraConnection -) extends Database[InterlibrDatabase](connector) { - object Envelopes extends EnvelopesTable with Connector - - def storeEnvelope(e: Envelope): Future[ResultSet] = { - Envelopes.storeRecord(e) - } -} - -object ConnectedInterlibrDatabase extends InterlibrDatabase(connectors.ContactPoint.local.keySpace("xadf")) diff --git a/app/services/InjectableMongo.scala b/app/services/InjectableMongo.scala deleted file mode 100644 index 0999507..0000000 --- a/app/services/InjectableMongo.scala +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package services - -import javax.inject._ - -// ours -import org.xalgorithms.storage.data.Mongo - -@Singleton -class InjectableMongo extends Mongo(new LocalLogger(), sys.env.get("MONGO_URL"), sys.env.get("MONGO_DATABASE")) { -} diff --git a/app/services/LocalLogger.scala b/app/services/LocalLogger.scala deleted file mode 100644 index b7dff37..0000000 --- a/app/services/LocalLogger.scala +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package services - -import org.xalgorithms.storage.data.{ Logger } - -import play.api.{ Logger => PlayLogger } - -class LocalLogger extends Logger { - def debug(m: String) = { - PlayLogger.debug(m) - } - - def error(m: String) = { - PlayLogger.error(m) - } - - def info(m: String) = { - PlayLogger.info(m) - } -} diff --git a/build.sbt b/build.sbt index a68ae08..6fbf61a 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,6 @@ // You should have received a copy of the GNU Affero General Public // License along with this program. If not, see // . -lazy val VERSION_MONGO_SCALA = "2.4.2" lazy val VERSION_SCALA = "2.12.4" lazy val VERSION_SCALA_TEST = "3.1.2" lazy val VERSION_CASSANDRA = "3.5.0" @@ -32,9 +31,6 @@ lazy val VERSION_PHANTOM = "2.24.0" lazy val VERSION_JODA = "2.10" lazy val VERSION_JODA_CONVERT = "2.1" -// ours -lazy val VERSION_STORAGE = "0.0.8" - lazy val meta = Seq( name := """service-il-schedule""", organization := "org.xalgorithms", @@ -44,8 +40,6 @@ lazy val meta = Seq( lazy val lib_deps = Seq( guice, - "org.xalgorithms" %% "il-storage" % VERSION_STORAGE from s"https://github.com/Xalgorithms/lib-storage/releases/download/v${VERSION_STORAGE}/il-storage_2.12-${VERSION_STORAGE}.jar", - "org.mongodb.scala" %% "mongo-scala-driver" % VERSION_MONGO_SCALA, "com.typesafe.akka" %% "akka-stream-kafka" % VERSION_AKKA_STREAM_KAFKA, "com.datastax.cassandra" % "cassandra-driver-core" % VERSION_CASSANDRA, "com.outworkers" %% "phantom-dsl" % VERSION_PHANTOM, diff --git a/files/submissions/submit.json b/files/submissions/submit.json index 998a90c..644f569 100644 --- a/files/submissions/submit.json +++ b/files/submissions/submit.json @@ -1,7 +1,13 @@ { "name" : "submit", - "args" : {}, - "payload" : { + "args" : { + "key" : "key0", + "country" : "CA", + "region" : "ON", + "timezone" : "America/Toronto", + "issued" : "2016-11-15T01:23:04-04:00" + }, + "document" : { "envelope" : { "issued" : "2016-11-15T01:23:04-04:00", "currency" : "CAD", diff --git a/files/submissions/submit.nodoc.json b/files/submissions/submit.nodoc.json new file mode 100644 index 0000000..ebd9eed --- /dev/null +++ b/files/submissions/submit.nodoc.json @@ -0,0 +1,5 @@ +{ + "name" : "submit", + "args" : { + } +} diff --git a/files/submissions/verify.applicable.json b/files/submissions/verify.applicable.json new file mode 100644 index 0000000..1f5eed8 --- /dev/null +++ b/files/submissions/verify.applicable.json @@ -0,0 +1,11 @@ +{ + "name" : "verify", + "args" : { + "what" : "applicable", + "rule_id": "1234" + }, + "document" : { + "a" : 1, + "b" : "2" + } +} diff --git a/files/submissions/verify.effective.json b/files/submissions/verify.effective.json new file mode 100644 index 0000000..5c0f784 --- /dev/null +++ b/files/submissions/verify.effective.json @@ -0,0 +1,15 @@ +{ + "name" : "verify", + "args" : { + "what" : "effective", + "key" : "key0", + "country" : "CA", + "region" : "ON", + "timezone" : "America/Toronto", + "issued" : "2016-11-15T01:23:04-04:00" + }, + "document" : { + "a" : 1, + "b" : "2" + } +} diff --git a/files/submissions/verify.nodoc.json b/files/submissions/verify.nodoc.json new file mode 100644 index 0000000..029ec68 --- /dev/null +++ b/files/submissions/verify.nodoc.json @@ -0,0 +1,6 @@ +{ + "name" : "verify", + "args" : { + "what" : "effective" + } +} diff --git a/files/submissions/verify.unknown.json b/files/submissions/verify.unknown.json new file mode 100644 index 0000000..84bad40 --- /dev/null +++ b/files/submissions/verify.unknown.json @@ -0,0 +1,8 @@ +{ + "name" : "verify", + "args" : { + "what" : "whoops" + }, + "document" : { + } +} diff --git a/test/models/DocumentEnvelopeSpec.scala b/test/models/DocumentEnvelopeSpec.scala deleted file mode 100644 index 6931999..0000000 --- a/test/models/DocumentEnvelopeSpec.scala +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import scala.io.Source -import org.bson.BsonDocument -import org.joda.time.DateTime - -import services.{ City, FindCountry, FindSubdivision, TimeZones } - -import org.scalamock.scalatest.MockFactory -import org.scalatest._ - -class DocumentEnvelopeSpec extends FlatSpec with Matchers with MockFactory { - val document_id = "1234" - - val expects = Map( - "0" -> Seq( - Envelope(document_id, "supplier", "CA", "CA-ON", "America/Toronto", new DateTime("2016-11-15T01:23:04-04:00")), - Envelope(document_id, "customer", "CA", "CA-ON", "America/Toronto", new DateTime("2016-11-15T01:23:04-04:00")) - ) - ) - - "DocumentEnvelope" should "extract all parties from a stored Document" in { - expects.foreach { case (k, ex) => - val s = Source.fromFile(s"files/envelope/${k}.json").mkString - val doc = BsonDocument.parse(s) - val timezones = mock[TimeZones] - val de = new DocumentEnvelope(document_id, doc, timezones) - - val country = FindCountry.by_code2("CA") - val subdivision = FindSubdivision.by_full_code("CA-ON") - val city = Some(City("Ottawa")) - - (timezones.lookup _) - .expects(country, subdivision, city) - .repeat(ex.size) - .returning(Some("America/Toronto")) - - de.rows shouldEqual(ex) - } - } -} diff --git a/test/models/DocumentSpec.scala b/test/models/DocumentSpec.scala deleted file mode 100644 index 141093f..0000000 --- a/test/models/DocumentSpec.scala +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (C) 2018 Don Kelly - -// This file is part of Interlibr, a functional component of an -// Internet of Rules (IoR). - -// ACKNOWLEDGEMENTS -// Funds: Xalgorithms Foundation -// Collaborators: Don Kelly, Joseph Potvin and Bill Olders. - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU Affero General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public -// License along with this program. If not, see -// . -package models - -import scala.io.Source -import org.bson._ -import org.joda.time.DateTime -import play.api.libs.json._ - -import org.scalamock.scalatest.MockFactory -import org.scalatest._ - -import models._ - -class DocumentSpec extends FlatSpec with Matchers with MockFactory { - val doc = BsonDocument.parse(Source.fromFile(s"files/document/0.json").mkString) - - "Document" should "find text elements" in { - val expects = Map( - "a" -> Some("AA"), - "b" -> Some("BB"), - "c" -> None, - "c.x.p" -> Some("PP") - ) - - expects.foreach { case (k, ex) => - Document.maybe_find_text(doc, k) shouldEqual(ex) - } - } - - it should "find many text elements" in { - val expects = Seq( - Tuple2(Seq("a", "b"), Seq("AA", "BB")), - Tuple2(Seq("a", "c"), Seq("AA")), - Tuple2(Seq("a", "s", "b", "t", "z"), Seq("AA", "BB")), - Tuple2(Seq("c.x.p", "c.x.q"), Seq("PP", "QQ")) - ) - - expects.foreach { tup => - Document.maybe_find_many_text(doc, tup._1) shouldEqual(tup._2) - } - } - - it should "find the first of many text elements" in { - val expects = Seq( - Tuple2(Seq("a", "b"), Some("AA")), - Tuple2(Seq("c", "a"), Some("AA")), - Tuple2(Seq("b", "s", "a", "t", "z"), Some("BB")), - Tuple2(Seq("c.x.p", "c.x.q"), Some("PP")) - ) - - expects.foreach { tup => - Document.maybe_find_first_text(doc, tup._1) shouldEqual(tup._2) - } - } - - it should "find document elements" in { - val expects = Map( - "a" -> None, - "b" -> None, - "c" -> Option(doc.getDocument("c", null)), - "c.x" -> Option(doc.getDocument("c", null).getDocument("x", null)), - "c.z" -> None - ) - - expects.foreach { case (k, ex) => - Document.maybe_find_document(doc, k) shouldEqual(ex) - } - } - - it should "find many document elements" in { - val expects = Seq( - Tuple2(Seq("a", "b"), Seq()), - Tuple2(Seq("a", "c"), Seq(doc.getDocument("c", null))), - Tuple2(Seq("a", "s", "c", "f"), Seq(doc.getDocument("c", null), doc.getDocument("f", null))), - Tuple2(Seq("c.x", "c.y"), Seq(doc.getDocument("c", null).getDocument("x", null))) - ) - - expects.foreach { tup => - Document.maybe_find_many_document(doc, tup._1) shouldEqual(tup._2) - } - } - - it should "find the first of many document elements" in { - val expects = Seq( - Tuple2(Seq("a", "b"), None), - Tuple2(Seq("a", "c"), Some(doc.getDocument("c", null))), - Tuple2(Seq("a", "s", "c", "f"), Some(doc.getDocument("c", null))), - Tuple2(Seq("c.x", "c.y"), Some(doc.getDocument("c", null).getDocument("x", null))) - ) - - expects.foreach { tup => - Document.maybe_find_first_document(doc, tup._1) shouldEqual(tup._2) - } - } - - it should "find date time elements (as strings)" in { - val expects = Map( - "a" -> None, - "b" -> None, - "c" -> None, - "d" -> Some(new DateTime("2018-06-12T11:24:47Z")), - "e" -> Some(new DateTime("2018-06-12T11:24:58Z")) - ) - - expects.foreach { case (k, ex) => - Document.maybe_find_date_time(doc, k) shouldEqual(ex) - } - - val dt = new DateTime() - val ndoc = new BsonDocument() - val secs = dt.getMillis / 1000 - - ndoc.append("a", new BsonTimestamp(secs.toInt, 0)) - ndoc.append("b", new BsonDateTime(dt.getMillis)) - - - Document.maybe_find_date_time(ndoc, "a") shouldEqual(Some(new DateTime(secs * 1000))) - Document.maybe_find_date_time(ndoc, "b") shouldEqual(Some(dt)) - } -}