Skip to content

Commit

Permalink
Add support to a new type of InputValue
Browse files Browse the repository at this point in the history
  • Loading branch information
nadouani committed Nov 15, 2016
1 parent c880eef commit 0430911
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 39 deletions.
18 changes: 18 additions & 0 deletions app/org/elastic4play/controllers/Fields.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import play.api.libs.streams.Accumulator
import play.api.mvc.{ BodyParser, MultipartFormData, RequestHeader }

import org.elastic4play.BadRequestError
import org.elastic4play.services.Attachment
import org.elastic4play.utils.Hash

import JsonFormat.{ fieldsReader, pathFormat }

Expand Down Expand Up @@ -48,6 +50,22 @@ case class FileInputValue(name: String, filepath: Path, contentType: String) ext
def jsonValue: JsObject = Json.obj("name" -> name, "filepath" -> filepath, "contentType" -> contentType)
}

/**
* Define an attachment that is already in datastore. This type can't be from HTTP request.
*/
case class AttachmentInputValue(name: String, hashes: Seq[Hash], size: Long, contentType: String, id: String) extends InputValue {
def jsonValue: JsObject = Json.obj(
"name" -> name,
"hashes" -> hashes.map(_.toString),
"size" -> size,
"contentType" -> contentType,
"id" -> id)
def toAttachment = Attachment(name, hashes, size, contentType, id)
}
object AttachmentInputValue {
def apply(attachment: Attachment) = new AttachmentInputValue(attachment.name, attachment.hashes, attachment.size, attachment.contentType, attachment.id)
}

/**
* Define a data value from HTTP request as null (empty value)
*/
Expand Down
34 changes: 24 additions & 10 deletions app/org/elastic4play/controllers/JsonFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package org.elastic4play.controllers
import java.io.File
import java.nio.file.{ Path, Paths }

import play.api.libs.json.{ Format, JsError, JsObject, JsString, JsSuccess }
import play.api.libs.json.{ Format, JsError, JsObject, JsString, JsSuccess, JsValue }
import play.api.libs.json.{ Reads, Writes }
import play.api.libs.json.JsValue.jsValueToJsLookup
import play.api.libs.json.Json
import play.api.libs.json.Json.toJsFieldJsValueWrapper
import play.api.libs.json.JsValue

import org.elastic4play.utils.Hash

object JsonFormat {

Expand All @@ -31,23 +32,36 @@ object JsonFormat {
} yield FileInputValue(name, filepath, contentType)
}

val attachmentInputValueReads = Reads[AttachmentInputValue] { json =>
for {
name <- (json \ "name").validate[String]
hashes <- (json \ "hashes").validate[Seq[String]]
size <- (json \ "size").validate[Long]
contentType <- (json \ "contentType").validate[String]
id <- (json \ "id").validate[String]
} yield AttachmentInputValue(name, hashes.map(Hash.apply), size, contentType, id)
}

val inputValueWrites = Writes[InputValue]((value: InputValue) => value match {
case v: StringInputValue => Json.obj("type" -> "StringInputValue", "value" -> v.jsonValue)
case v: JsonInputValue => Json.obj("type" -> "JsonInputValue", "value" -> v.jsonValue)
case v: FileInputValue => Json.obj("type" -> "FileInputValue", "value" -> v.jsonValue)
case NullInputValue => Json.obj("type" -> "NullInputValue")
case v: StringInputValue => Json.obj("type" -> "StringInputValue", "value" -> v.jsonValue)
case v: JsonInputValue => Json.obj("type" -> "JsonInputValue", "value" -> v.jsonValue)
case v: FileInputValue => Json.obj("type" -> "FileInputValue", "value" -> v.jsonValue)
case v: AttachmentInputValue => Json.obj("type" -> "AttachmentInputValue", "value" -> v.jsonValue)
case NullInputValue => Json.obj("type" -> "NullInputValue")
})

val inputValueReads = Reads { json =>
(json \ "type").validate[String].flatMap {
case "StringInputValue" => (json \ "value").validate(stringInputValueReads)
case "JsonInputValue" => (json \ "value").validate(jsonInputValueReads)
case "FileInputValue" => (json \ "value").validate(fileInputValueReads)
case "NullInputValue" => new JsSuccess(NullInputValue)
case "StringInputValue" => (json \ "value").validate(stringInputValueReads)
case "JsonInputValue" => (json \ "value").validate(jsonInputValueReads)
case "FileInputValue" => (json \ "value").validate(fileInputValueReads)
case "AttachmentInputValue" => (json \ "value").validate(attachmentInputValueReads)
case "NullInputValue" => new JsSuccess(NullInputValue)
}
}

implicit val fileInputValueFormat = Format[FileInputValue](fileInputValueReads, fileInputValueWrites)

implicit val inputValueFormat = Format[InputValue](inputValueReads, inputValueWrites)

implicit val fieldsReader = Reads {
Expand Down
22 changes: 11 additions & 11 deletions app/org/elastic4play/models/Attributes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@ package org.elastic4play.models

import java.util.{ Date, Map, UUID }

import javax.inject.Singleton

import scala.language.{ existentials, implicitConversions, postfixOps }
import scala.math.BigDecimal.{ int2bigDecimal, long2bigDecimal }
import scala.reflect.ClassTag
import scala.util.Try

import play.api.Logger
import play.api.libs.json.{ Format, JsArray, JsBoolean, JsNull, JsNumber, JsObject, JsString, JsSuccess, JsValue, Json }
import play.api.libs.json.Json.toJsFieldJsValueWrapper

import org.elastic4play.{ AttributeError, InvalidFormatAttributeError }
import org.elastic4play.{ MissingAttributeError, UnknownAttributeError, UpdateReadOnlyAttributeError }
import org.elastic4play.{ AttributeError, InvalidFormatAttributeError, MissingAttributeError, UnknownAttributeError, UpdateReadOnlyAttributeError }
import org.elastic4play.JsonFormat.dateFormat
import org.elastic4play.controllers.{ FileInputValue, InputValue }
import org.elastic4play.controllers.{ JsonInputValue, NullInputValue, StringInputValue }
import org.elastic4play.controllers.{ AttachmentInputValue, FileInputValue, InputValue, JsonInputValue, NullInputValue, StringInputValue }
import org.elastic4play.controllers.JsonFormat.{ fileInputValueFormat, inputValueFormat }
import org.elastic4play.models.JsonFormat.{ binaryFormats, multiFormat, optionFormat }
import org.elastic4play.services.{ Attachment, DBLists }
Expand All @@ -25,9 +22,8 @@ import org.scalactic.{ Bad, Every, Good, One, Or }
import org.scalactic.Accumulation.convertGenTraversableOnceToValidatable

import com.sksamuel.elastic4s.ElasticDsl.field
import com.sksamuel.elastic4s.mappings.{ TypedFieldDefinition, attributes }
import com.sksamuel.elastic4s.mappings.FieldType.{ BinaryType, BooleanType, DateType, LongType, NestedType, ObjectType, StringType }
import play.api.Logger
import com.sksamuel.elastic4s.mappings.{ attributes, TypedFieldDefinition }

abstract class AttributeFormat[T](val name: String)(implicit val jsFormat: Format[T]) {
def checkJson(subNames: Seq[String], value: JsValue): JsValue Or Every[AttributeError]
Expand Down Expand Up @@ -263,9 +259,12 @@ object HashAttributeFormat extends AttributeFormat[String]("hash") {
}

object AttachmentAttributeFormat extends AttributeFormat[Attachment]("attachment") {
override def checkJson(subNames: Seq[String], value: JsValue) = fileInputValueFormat.reads(value) match {
case JsSuccess(_, _) if subNames.isEmpty => Good(value)
case _ => Bad(One(InvalidFormatAttributeError("", name, JsonInputValue(value))))
override def checkJson(subNames: Seq[String], value: JsValue) = {
lazy val validJson = fileInputValueFormat.reads(value).asOpt orElse jsFormat.reads(value).asOpt
if (subNames.isEmpty && validJson.isDefined)
Good(value)
else
Bad(One(InvalidFormatAttributeError("", name, JsonInputValue(value))))
}
val forbiddenChar = Seq('/', '\n', '\r', '\t', '\u0000', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':', ';')
override def inputValueToJson(subNames: Seq[String], value: InputValue): JsValue Or Every[AttributeError] = {
Expand All @@ -274,6 +273,7 @@ object AttachmentAttributeFormat extends AttributeFormat[Attachment]("attachment
else
value match {
case fiv: FileInputValue if fiv.name.intersect(forbiddenChar).isEmpty => Good(Json.toJson(fiv)(fileInputValueFormat))
case aiv: AttachmentInputValue => Good(Json.toJson(aiv.toAttachment)(jsFormat))
case _ => Bad(One(InvalidFormatAttributeError("", name, value)))
}
}
Expand Down
36 changes: 20 additions & 16 deletions app/org/elastic4play/services/AttachmentSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import play.api.libs.json.Json
import play.api.libs.json.Json.toJsFieldJsValueWrapper

import org.elastic4play.{ AttributeCheckingError, InvalidFormatAttributeError, MissingAttributeError }
import org.elastic4play.controllers.FileInputValue
import org.elastic4play.controllers.JsonFormat.fileInputValueFormat
import org.elastic4play.controllers.{ AttachmentInputValue, FileInputValue }
import org.elastic4play.controllers.JsonFormat.{ attachmentInputValueReads, fileInputValueFormat }
import org.elastic4play.controllers.JsonInputValue
import org.elastic4play.database.DBCreate
import org.elastic4play.models.{ AttributeDef, AttributeFormat => F, BaseModelDef, EntityDef, ModelDef }
Expand Down Expand Up @@ -78,24 +78,28 @@ class AttachmentSrv(mainHash: String,
case (attributes, (name, isRequired)) =>
attributes.flatMap { a =>
// try to convert in FileInputValue Scala Object
(a \ name).asOpt[FileInputValue] match {
case Some(attach) =>
val inputValue = (a \ name).asOpt[FileInputValue] orElse (a \ name).asOpt[AttachmentInputValue](attachmentInputValueReads)
inputValue
.map {
// save attachment and replace FileInputValue json representation to JsObject containing attachment attributes
save(attach).map { attachment =>
case fiv: FileInputValue => save(fiv).map { attachment =>
a - name + (name -> Json.toJson(attachment))
}
// if conversion to FileInputValue fails, it means that attribute is missing of format is invalid
case _ => (a \ name).asOpt[JsValue] match {
case Some(v) if v != JsNull && v != JsArray(Nil) =>
Future.failed(AttributeCheckingError(model.name, Seq(
InvalidFormatAttributeError(name, "attachment", (a \ name).asOpt[FileInputValue].getOrElse(JsonInputValue((a \ name).as[JsValue]))))))
case _ =>
if (isRequired)
Future.failed(AttributeCheckingError(model.name, Seq(MissingAttributeError(name))))
else
Future.successful(a)
case aiv: AttachmentInputValue => Future.successful(a - name + (name -> Json.toJson(aiv.toAttachment)))
}
// if conversion to FileInputValue fails, it means that attribute is missing or format is invalid
.getOrElse {
(a \ name).asOpt[JsValue] match {
case Some(v) if v != JsNull && v != JsArray(Nil) =>
Future.failed(AttributeCheckingError(model.name, Seq(
InvalidFormatAttributeError(name, "attachment", (a \ name).asOpt[FileInputValue].getOrElse(JsonInputValue((a \ name).as[JsValue]))))))
case _ =>
if (isRequired)
Future.failed(AttributeCheckingError(model.name, Seq(MissingAttributeError(name))))
else
Future.successful(a)
}
}
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion app/org/elastic4play/services/CreateSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ class CreateSrv @Inject() (fieldsSrv: FieldsSrv,
.map(_.collect {
case (name, Some(value)) => name -> value
})
.fold(attrs => Future.successful(JsObject(attrs.toSeq)), errors => Future.failed(AttributeCheckingError(model.name, errors)))
.fold(
attrs => Future.successful(JsObject(attrs.toSeq)),
errors => Future.failed(AttributeCheckingError(model.name, errors)))
}
private[services] def processAttributes(model: BaseModelDef, parent: Option[BaseEntity], attributes: JsObject)(implicit authContext: AuthContext): Future[JsObject] = {
for {
Expand Down
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version := "1.1.0"
version := "1.1.1-AIV-SNAPSHOT"

0 comments on commit 0430911

Please sign in to comment.