Skip to content

Commit

Permalink
Add reference from Configuration to Method.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dave Shiga committed Aug 20, 2015
1 parent 7022e35 commit fec649b
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 59 deletions.
Expand Up @@ -4,20 +4,32 @@ import cromwell.parser.WdlParser.SyntaxError
import org.broadinstitute.dsde.agora.server.webservice.util.{DockerImageReference, DockerHubClient}

import cromwell.binding._
import org.broadinstitute.dsde.agora.server.dataaccess.AgoraDao
import org.broadinstitute.dsde.agora.server.dataaccess.{AgoraEntityNotFoundException, AgoraDao}
import org.broadinstitute.dsde.agora.server.dataaccess.permissions.NamespacePermissionsClient
import org.broadinstitute.dsde.agora.server.dataaccess.permissions.AgoraEntityPermissionsClient
import org.broadinstitute.dsde.agora.server.dataaccess.permissions._
import org.broadinstitute.dsde.agora.server.dataaccess.permissions.AgoraPermissions._
import org.broadinstitute.dsde.agora.server.model.{AgoraEntity, AgoraEntityProjection, AgoraEntityType}
import org.broadinstitute.dsde.agora.server.model.{AgoraApiJsonSupport, AgoraEntity, AgoraEntityProjection, AgoraEntityType}
import spray.json
import spray.json._
import DefaultJsonProtocol._

class AgoraBusiness {

def insert(agoraEntity: AgoraEntity, username: String): AgoraEntity = {
if (NamespacePermissionsClient.getNamespacePermission(agoraEntity, username).canCreate) {
validatePayload(agoraEntity, username)

val entityWithId = AgoraDao.createAgoraDao(agoraEntity.entityType).insert(agoraEntity.addDate())
val entityToInsert = agoraEntity.entityType.get match {
case AgoraEntityType.Configuration =>
val method = resolveMethodRef(agoraEntity.payload.get)
if (!AgoraEntityPermissionsClient.getEntityPermission(method, username).canRead) {
throw new AgoraEntityNotFoundException(method)
}
agoraEntity.addMethodId(method.id.get.toHexString)
case _ => agoraEntity
}
val entityWithId = AgoraDao.createAgoraDao(entityToInsert.entityType).insert(entityToInsert.addDate())
val userAccess = new AccessControl(username, AgoraPermissions(All))

if (!NamespacePermissionsClient.doesEntityExists(agoraEntity)) {
Expand All @@ -27,9 +39,7 @@ class AgoraBusiness {

AgoraEntityPermissionsClient.addEntity(entityWithId)
AgoraEntityPermissionsClient.insertEntityPermission(entityWithId, userAccess)

entityWithId.addUrl()

entityWithId.addUrl().removeIds()
} else {
throw new NamespaceAuthorizationException(AgoraPermissions(Create), agoraEntity, username)
}
Expand All @@ -42,7 +52,7 @@ class AgoraBusiness {

val entities = AgoraDao.createAgoraDao(entityTypes)
.find(agoraSearch, agoraProjection)
.map(entity => entity.addUrl())
.map(entity => entity.addUrl().removeIds())

AgoraEntityPermissionsClient.filterEntityByRead(entities, username)
}
Expand All @@ -55,7 +65,7 @@ class AgoraBusiness {
val foundEntity = AgoraDao.createAgoraDao(entityTypes).findSingle(namespace, name, snapshotId)

if (AgoraEntityPermissionsClient.getEntityPermission(foundEntity, username).canRead)
foundEntity.addUrl()
foundEntity.addUrl().removeIds()
else
throw new EntityAuthorizationException(AgoraPermissions(Read), foundEntity, username)
}
Expand All @@ -76,7 +86,13 @@ class AgoraBusiness {
// Passed basic validation. Now check if (any) docker images that are referenced exist
namespace.tasks.foreach { validateDockerImage }
case AgoraEntityType.Configuration =>
//add config validation here
val json = agoraEntity.payload.get.parseJson
val fields = json.asJsObject.getFields("methodStoreMethod")
require(fields.size == 1)
val subFields = fields(0).asJsObject.getFields("methodNamespace", "methodName", "methodVersion")
require(subFields(0).isInstanceOf[JsString])
require(subFields(1).isInstanceOf[JsString])
require(subFields(2).isInstanceOf[JsNumber])
}
}

Expand All @@ -89,6 +105,11 @@ class AgoraBusiness {
}
}

private def resolveMethodRef(payload: String): AgoraEntity = {
val queryMethod = AgoraApiJsonSupport.methodRef(payload)
AgoraDao.createAgoraDao(AgoraEntityType.MethodTypes).findSingle(queryMethod)
}

/**
* Parses out user/image:tag from a docker string.
*
Expand All @@ -112,4 +133,4 @@ class AgoraBusiness {
Option(DockerImageReference(user, repo, tag))
}
}
}
}
Expand Up @@ -34,6 +34,12 @@ object AgoraMongoClient {
mongoClient(AgoraConfig.mongoDbDatabase)(ConfigurationsCollection)
}

def getCollectionsByEntityType(entityTypes: Seq[AgoraEntityType.EntityType]): Seq[MongoCollection] = {
entityTypes.flatMap {
entityType => getCollectionsByEntityType(Option(entityType))
}
}

def getCollectionsByEntityType(entityType: Option[AgoraEntityType.EntityType]): Seq[MongoCollection] = {
entityType match {
case Some(AgoraEntityType.Task) =>
Expand Down
Expand Up @@ -12,7 +12,8 @@ import org.broadinstitute.dsde.agora.server.dataaccess.mongo.AgoraMongoDao._
import org.broadinstitute.dsde.agora.server.dataaccess.{AgoraDao, AgoraEntityNotFoundException}
import org.broadinstitute.dsde.agora.server.model.AgoraApiJsonSupport._
import org.broadinstitute.dsde.agora.server.model.AgoraEntityProjection._
import org.broadinstitute.dsde.agora.server.model.{AgoraEntity, AgoraEntityProjection}
import org.broadinstitute.dsde.agora.server.model.{AgoraEntityType, AgoraEntity, AgoraEntityProjection}
import org.bson.types.ObjectId
import spray.json._

object AgoraMongoDao {
Expand All @@ -26,7 +27,9 @@ object AgoraMongoDao {
JSON.parse(entity.copy(url = None).toJson.toString()).asInstanceOf[DBObject]
}

def MongoDbObjectToEntity(mongoDBObject: DBObject): AgoraEntity = mongoDBObject.toString.parseJson.convertTo[AgoraEntity]
def MongoDbObjectToEntity(mongoDBObject: DBObject): AgoraEntity = {
mongoDBObject.toString.parseJson.convertTo[AgoraEntity]
}
}

/**
Expand All @@ -36,11 +39,6 @@ object AgoraMongoDao {
*/
class AgoraMongoDao(collections: Seq[MongoCollection]) extends AgoraDao {

def assureSingleCollection: MongoCollection = {
if (collections.size != 1) throw new IllegalArgumentException("Multiple collections defined. Only a single collection is supported for this operation")
else collections.head
}

/**
* On insert we query for the given namespace/name if it exists we increment the snapshotId and store a new one.
* @param entity The entity to store.
Expand All @@ -55,7 +53,51 @@ class AgoraMongoDao(collections: Seq[MongoCollection]) extends AgoraDao {
//insert the entity
val dbEntityToInsert = EntityToMongoDbObject(entityWithId)
collection.insert(dbEntityToInsert)
findSingle(entityWithId)
val insertedEntity = findSingle(MongoDbObjectToEntity(dbEntityToInsert))
insertedEntity
}

override def findSingle(namespace: String, name: String, snapshotId: Int): AgoraEntity = {
val entity = AgoraEntity(namespace = Option(namespace), name = Option(name), snapshotId = Option(snapshotId))
findSingle(entity)
}

override def findSingle(entity: AgoraEntity): AgoraEntity = {
val dbEntity = EntityToMongoDbObject(entity)
val entityVector = find(dbEntity, None)
entityVector.length match {
case 1 =>
val foundEntity = entityVector.head
addMethodRef(Seq(foundEntity), None).head
case 0 => throw new AgoraEntityNotFoundException(entity)
case _ => throw new Exception("Found > 1 documents matching: " + entity.toString)
}
}

override def find(entity: AgoraEntity, projectionOpt: Option[AgoraEntityProjection]): Seq[AgoraEntity] = {
val projection = projectionOpt match {
case Some(projection) => projectionOpt
case None => DefaultFindProjection
}
val dbEntity = EntityToMongoDbObject(entity)
val entities = find(dbEntity, projection)
addMethodRef(entities, projection)
}

def findById(ids: Seq[ObjectId], entityCollections: Seq[MongoCollection], projection: Option[AgoraEntityProjection]): Seq[AgoraEntity] = {
val dbQuery = (MongoDbIdField $in ids)
val entities = entityCollections.flatMap {
collection =>
collection.find(dbQuery, projectionToDBProjections(projection)).map {
dbObject => MongoDbObjectToEntity(dbObject)
}
}
entities
}

def assureSingleCollection: MongoCollection = {
if (collections.size != 1) throw new IllegalArgumentException("Multiple collections defined. Only a single collection is supported for this operation")
else collections.head
}

def getNextId(entity: AgoraEntity): Int = {
Expand All @@ -77,22 +119,6 @@ class AgoraMongoDao(collections: Seq[MongoCollection]) extends AgoraDao {
currentCount.get(CounterSequenceField).asInstanceOf[Int]
}

override def findSingle(entity: AgoraEntity): AgoraEntity = {
val entityVector = find(EntityToMongoDbObject(entity), None)
entityVector.length match {
case 1 => entityVector.head
case 0 => throw new AgoraEntityNotFoundException(entity)
case _ => throw new Exception("Found > 1 documents matching: " + entity.toString)
}
}

override def find(entity: AgoraEntity, projectionOpt: Option[AgoraEntityProjection]): Seq[AgoraEntity] = {
projectionOpt match {
case Some(projection) => find(EntityToMongoDbObject(entity), projectionOpt)
case None => find(EntityToMongoDbObject(entity), DefaultFindProjection)
}
}

def find(query: Imports.DBObject, projection: Option[AgoraEntityProjection]) = {
collections.flatMap {
collection =>
Expand All @@ -119,8 +145,37 @@ class AgoraMongoDao(collections: Seq[MongoCollection]) extends AgoraDao {
}
}

override def findSingle(namespace: String, name: String, snapshotId: Int): AgoraEntity = {
val entity = AgoraEntity(namespace = Option(namespace), name = Option(name), snapshotId = Option(snapshotId))
findSingle(entity)
// Populate method field within configurations
def addMethodRef(entities: Seq[AgoraEntity], projection: Option[AgoraEntityProjection]): Seq[AgoraEntity] = {
// Don't bother trying to populate method refs unless there are configs in the result set (only configs have embedded methods)
val configCollection = getCollectionsByEntityType(Seq(AgoraEntityType.Configuration)).head
if (!collections.contains(configCollection)) {
return entities
}

// Get map of method ids to Methods
val methodRefs = methodIds(entities)
val methodCollections = getCollectionsByEntityType(AgoraEntityType.MethodTypes)
val methods = findById(methodRefs, methodCollections, projection)
val methodsMap = idToEntityMap(methods, methodCollections)

// Populate method field if methodId has a value
val entitiesWithRefs = entities.map {
entity =>
if (entity.methodId.nonEmpty) {
val method = methodsMap.get(entity.methodId.get)
entity.addMethod(Option(method.get.addUrl().removeIds()))
}
else {
entity
}
}
entitiesWithRefs
}

def methodIds(entities: Seq[AgoraEntity]): Seq[ObjectId] = entities.flatMap { entity => entity.methodId }

def idToEntityMap(entities: Seq[AgoraEntity], entityCollections: Seq[MongoCollection]): Map[ObjectId, AgoraEntity] = {
entities.map { entity => entity.id.get -> entity }.toMap
}
}
Expand Up @@ -3,6 +3,7 @@ package org.broadinstitute.dsde.agora.server.model

import org.broadinstitute.dsde.agora.server.webservice.util.AgoraOpenAMClient.UserInfoResponse
import org.broadinstitute.dsde.agora.server.dataaccess.permissions.{AgoraPermissions, AccessControl}
import org.bson.types.ObjectId
import org.joda.time.DateTime
import org.joda.time.format.{DateTimeFormatter, ISODateTimeFormat}
import spray.json._
Expand All @@ -15,6 +16,16 @@ object AgoraApiJsonSupport extends DefaultJsonProtocol {

implicit def stringToType(str: String): AgoraEntityType.EntityType = AgoraEntityType.withName(str)

implicit object ObjectIdJsonFormat extends RootJsonFormat[ObjectId] {
override def write(obj: ObjectId) = {
JsObject("$oid" -> JsString(obj.toHexString))
}

override def read(json: JsValue): ObjectId = {
new ObjectId(json.asJsObject.fields("$oid").convertTo[String])
}
}

implicit object DateJsonFormat extends RootJsonFormat[DateTime] {
override def write(obj: DateTime) = {
JsString(parserISO.print(obj))
Expand Down Expand Up @@ -54,8 +65,57 @@ object AgoraApiJsonSupport extends DefaultJsonProtocol {
}
}

private val parserISO: DateTimeFormatter = {
ISODateTimeFormat.dateTimeNoMillis()
implicit object AgoraEntityFormat extends RootJsonFormat[AgoraEntity] {

override def write(entity: AgoraEntity) = {
var map = Map.empty[String, JsValue]
if (entity.namespace.nonEmpty) map += ("namespace" -> JsString(entity.namespace.get))
if (entity.name.nonEmpty) map += ("name" -> JsString(entity.name.get))
if (entity.snapshotId.nonEmpty) map += ("snapshotId" -> JsNumber(entity.snapshotId.get))
if (entity.synopsis.nonEmpty) map += ("synopsis" -> JsString(entity.synopsis.get))
if (entity.documentation.nonEmpty) map += ("documentation" -> JsString(entity.documentation.get))
if (entity.owner.nonEmpty) map += ("owner" -> JsString(entity.owner.get))
if (entity.createDate.nonEmpty) map += ("createDate" -> entity.createDate.get.toJson)
if (entity.payload.nonEmpty) map += ("payload" -> JsString(entity.payload.get))
if (entity.url.nonEmpty) map += ("url" -> JsString(entity.url.get))
if (entity.entityType.nonEmpty) map += ("entityType" -> entity.entityType.get.toJson)
if (entity.id.nonEmpty) map += ("_id" -> entity.id.get.toJson)
if (entity.methodId.nonEmpty) map += ("methodId" -> entity.methodId.get.toJson)
if (entity.method.nonEmpty) map += ("method" -> entity.method.get.toJson)
JsObject(map)
}

override def read(json: JsValue): AgoraEntity = {
val jsObject = json.asJsObject
val namespace = stringOrNone(jsObject, "namespace")
val name = stringOrNone(jsObject, "name")
val snapshotId = if (jsObject.getFields("snapshotId").nonEmpty) jsObject.fields("snapshotId").convertTo[Option[Int]] else None
val synopsis = stringOrNone(jsObject, "synopsis")
val documentation = stringOrNone(jsObject, "documentation")
val owner = stringOrNone(jsObject, "owner")
val createDate = if (jsObject.getFields("createDate").nonEmpty) jsObject.fields("createDate").convertTo[Option[DateTime]] else None
val payload = stringOrNone(jsObject, "payload")
val url = stringOrNone(jsObject, "url")
val entityType = if (jsObject.getFields("entityType").nonEmpty) jsObject.fields("entityType").convertTo[Option[AgoraEntityType.EntityType]] else None
val id = if (jsObject.getFields("_id").nonEmpty) jsObject.fields("_id").convertTo[Option[ObjectId]] else None
val methodId = if (jsObject.getFields("methodId").nonEmpty) jsObject.fields("methodId").convertTo[Option[ObjectId]] else None
val method = if (jsObject.getFields("method").nonEmpty) jsObject.fields("method").convertTo[Option[AgoraEntity]] else None

val entity = AgoraEntity(namespace = namespace,
name = name,
snapshotId = snapshotId,
synopsis = synopsis,
documentation = documentation,
owner = owner,
createDate = createDate,
payload = payload,
url = url,
entityType = entityType,
id = id,
methodId = methodId,
method = method)
entity
}
}

implicit object AgoraPermissionsFormat extends RootJsonFormat[AgoraPermissions] {
Expand All @@ -71,8 +131,23 @@ object AgoraApiJsonSupport extends DefaultJsonProtocol {

}

implicit val AgoraEntityFormat = jsonFormat10(AgoraEntity.apply)
def methodRef(payload: String): AgoraEntity = {
val json = payload.parseJson
val refJson = json.asJsObject.fields("methodStoreMethod").asJsObject
val namespace = refJson.fields("methodNamespace").convertTo[String]
val name = refJson.fields("methodName").convertTo[String]
val snapshotId = refJson.fields("methodVersion").convertTo[Int]
AgoraEntity(namespace = Option(namespace), name = Option(name), snapshotId = Option(snapshotId))
}

private def stringOrNone(json: JsObject, key: String): Option[String] = {
if (json.getFields(key).nonEmpty) json.fields(key).convertTo[Option[String]] else None
}

private val parserISO: DateTimeFormatter = {
ISODateTimeFormat.dateTimeNoMillis()
}

implicit val AgoraEntityProjectionFormat = jsonFormat2(AgoraEntityProjection.apply)

implicit val AgoraErrorFormat = jsonFormat1(AgoraError)
Expand Down

0 comments on commit fec649b

Please sign in to comment.