store (binary) file - play framework using scala in heroku - scala

I'm trying to store user-uploaded images in my application which is written by scala and play framework 2.2.x
I've deployed my app in heroku.
Heroku does not allow me to save my file in file system.
So I've tried to store my file in data base.
here is the code that I use for storing image :
def updateImage(id: Long, image: Array[Byte]) = {
val selected = getById(id)
DB.withConnection {
implicit c =>
SQL("update subcategory set image={image} where id = {id}").on('id -> id, 'image -> image).executeUpdate()
}
selected }
and here is the code that I use to retreive my image :
def getImageById(id: Long): Array[Byte] = DB.withConnection {
implicit c =>
val all = SQL("select image from subcategory where id = {id}").on('id -> id)().map {
case Row(image: Array[Byte]) => image
case Row(Some(image: Array[Byte])) => image
case Row(image: java.sql.Blob )=> image.getBytes(0 , image.length().toInt)
}
all.head
}
The problem is: when I use H2 database and blob column, I get the "Match Error" exception.
When I use Postgresql and bytea column, I got no error but when I retrieve the image, It's in hex format and some of the bytes in the beginning of the array are missing.

According to the PostgreSQL documentation, bytea stores the length of the array in the four bytes at the beginning of the array. These are stripped when you read the row, so that's why they seem to be "missing" when you compare the data in Scala with the data in the DB.
You will have to set the response's content-type to the appropriate value if you want the web browser to display the image correctly, as otherwise it does not know it is receiving image data. The Ok.sendFile helper does it for you. Otherwise you will have to do it by hand:
def getPicture = Action {
SimpleResult(
header = ResponseHeader(200),
body = Enumerator(pictureByteArray))
.as(pictureContentType)
}
In the example above, pictureByteArray is the Array[Byte] containing the picture data from your database, and pictureContentType is a string with the appropriate content type (for example, image/jpeg).
This is all quite well explained in the Play documentation.

Related

Akka flow Input (`In`) as Output (`Out`)

I am trying to write a piece of code which does following:-
Reads a large csv file from remote source like s3.
Process the file record by record.
Send notification to user
Write the output to a remote location
Sample record in input csv:
recordId,name,salary
1,Aiden,20000
2,Tom,18000
3,Jack,25000
My input case class which represents a record in input csv:
case class InputRecord(recordId: String, name: String, salary: Long)
Sample record in output csv (that needs to be written):
recordId,name,designation
1,Aiden,Programmer
2,Tom,Web Developer
3,Jack,Manager
My output case class which represents a record in input csv:
case class OutputRecord(recordId: String, name: String, designation: String)
Reading a record using akka stream csv (uses Alpakka reactive s3 https://doc.akka.io/docs/alpakka/current/s3.html):
def readAsCSV: Future[Source[Map[String, ByteString], NotUsed]] =
S3.download(s3Object.bucket, s3Object.path)
.runWith(Sink.head)
// This is then converted to csv
Now I have a function to process the records:
def process(input: InputRecord): OutputRecord =
//if salary > avg(salary) then Manager
//else Programmer
Function to write the OutputRecord as csv
def writeOutput:Sink[ByteString, Future[MultipartUploadResult]] =
S3.multipartUpload(s3Object.bucket,
s3Object.path,
metaHeaders = MetaHeaders(Map())
Function to send email notification:
def notify : Flow[OutputRecord, PushResult, NotUsed]
//if notification is sent successfully PushResult has some additional info
Stitching it all together
readAsCSV.flatMap { recordSource =>
recordSource.map { record
val outputRecord = process(record)
outputRecord
}
.via(notify) //Error: Line 15
.to(writeOutput) //Error: Line 16
.run()
}
On Line 15 & 16 I am getting an error, I am either able to add Line 15 or Line 16 but not both since both notify & writeOutput needs outputRecord. Once notify is called I loose my outputRecord.
Is there a way I can add both notify and writeOutput to same graph?
I am not looking for parallel execution as I want to first call notify and then only writeOutput. So this is not helpful: https://doc.akka.io/docs/akka/current/stream/stream-parallelism.html#parallel-processing
The use case seems very simple to me but some how I am not able to find a clean solution.
The output of notify is a PushResult, but the input of writeOutput is ByteString. Once you change that it will compile. In case you need ByteString, get the same from OutputRecord.
BTW, in the sample code that you have provided, a similar error exists in readCSV and process.

How do I save the file name in the database

When I add a new record in a database using a form, I also can upload an image. These two are not linked together; record goes in database and the image goes in an folder on my desktop, so to know which image belongs to which record, I want to put the filename in a column. How do i approach this?
Im using PlayFramework 2.4, Scala, H2 Database and Anorm for my Project
In your html form you need to have an input tag of file type, something like:
<input type="file" name="picture">
And in your scala method Controller, where you are getting the form submit, something like:
def save = Action(parse.multipartFormData) { request =>
request.body.file("picture").map { picture =>
import java.io.File
val filename = picture.filename
println(filename)
Ok("saved")
}
You could retrieve the absolute path of the file like so:
scala> val filePath = getClass.getResource("myImage.png")
filePath: java.net.URI = file:/home/robert/myImage.png

Play 2.3 scala forms - how to customise constraint messages

I've created form in play framework with constraints:
val voucherForm = Form(
mapping(
"voucherName" -> nonEmptyText,
"voucherCode" -> optional(text(minLength = 6).verifying(pattern("""[a-zA-Z0-9]+""".r, error = "...")))
)(VoucherForm.apply)(VoucherForm.unapply)
)
when I display this form on a web page I have constraint messages (like Required, Minimum length: 6, constraint.pattern) shown near input boxes.
I want to customise this constraint messages per input field (i.e. two nonEmptyText constraints in same form will have different constraint message). How could I do it?
Instead of using nonEmptyText, could you not use text, and put your custom message in the verifying, along the lines of:
val voucherForm = Form(
mapping(
"voucherName" -> text.verifying(
"Please specify a voucher name", f => f.trim!=""),
...
These messages are taken from, well, messages. You can create your custom messages file and put there your custom text. Navigate through sources to check what is the valid string to put there.
For example nonEmptyText is declared as follows:
val nonEmptyText: Mapping[String] = text verifying Constraints.nonEmpty
and from there, Constraints.nonEmpty looks like this:
def nonEmpty: Constraint[String] = Constraint[String]("constraint.required") { o =>
if (o == null) Invalid(ValidationError("error.required")) else if (o.trim.isEmpty) Invalid(ValidationError("error.required")) else Valid
}
so the error string is "error.required"
now you can create a file messages in conf directory and put there a line
error.required=This field is required
ValidationError has apply method declared like this:
def apply(message: String, args: Any*)
which means you can also pass arguments there, in messages you can access them using {arg_num} syntax
If you for example created error like this
val format = ???
ValidationError("error.time", someFormat)
That will be returned with a bounded form, then play will use MessagesApi to find a message named "error.time" and format it accordingly, you could for example create a message like this:
error.time=Expected time format is {0}
My idea to have a custom message for each field is a custom method like this:
def nonEmptyTextWithError(error: String): Mapping[String] = {
Forms.text verifying Constraint[String]("constraint.required") { o =>
if (o == null) Invalid(ValidationError(error)) else if (o.trim.isEmpty) Invalid(ValidationError(error)) else Valid
}
}
probably not an ideal solution if you want to use many kids of constraints.

Scala Play upload file within a form

How would I upload a file within a form defined with Scala Play's play.api.data.Forms framework. I want the file to be stored under Treatment Image.
val cForm: Form[NewComplication] = Form(
mapping(
"Name of Vital Sign:" -> of(Formats.longFormat),
"Complication Name:" -> text,
"Definition:" -> text,
"Reason:" -> text,
"Treatment:" -> text,
"Treatment Image:" -> /*THIS IS WHERE I WANT THE FILE*/,
"Notes:" -> text,
"Weblinks:" -> text,
"Upper or Lower Bound:" -> text)
(NewComplication.apply _ )(NewComplication.unapply _ ))
is there a simple way to do this? By using built in Formats?
I think you have to handle the file component of a multipart upload separately and combine it with your form data afterwards. You could do this several ways, depending on what you want the treatment image field to actually be (the file-path as a String, or, to take you literally, as a java.io.File object.)
For that last option, you could make the treatment image field of your NewComplication case class an Option[java.io.File] and handle it in your form mapping with ignored(Option.empty[java.io.File]) (so it won't be bound with the other data.) Then in your action do something like this:
def createPost = Action(parse.multipartFormData) { implicit request =>
request.body.file("treatment_image").map { picture =>
// retrieve the image and put it where you want...
val imageFile = new java.io.File("myFileName")
picture.ref.moveTo(imageFile)
// handle the other form data
cForm.bindFromRequest.fold(
errForm => BadRequest("Ooops"),
complication => {
// Combine the file and form data...
val withPicture = complication.copy(image = Some(imageFile))
// Do something with result...
Redirect("/whereever").flashing("success" -> "hooray")
}
)
}.getOrElse(BadRequest("Missing picture."))
}
A similar thing would apply if you wanted just to store the file path.
There are several ways to handle file upload which will usually depend on what you're doing with the files server-side, so I think this approach makes sense.

Converting ISO-8859-1 to UTF-8 for MultipartFormData in Play2 + Scala when parsing email from Sendgrid

I have hooked up my Play2+Scala application to Sendgrid Parse Api and I'm really struggling in decoding and encoding the content of the email.
Since the emails could be in different encodings Sendgrid provides us with a JSON object charsets:
{"to":"UTF-8","cc":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"iso-8859-1","html":"iso-8859-1"}
In my test case "text" is "Med Vänliga Hälsningar Jakobs Webshop"
If I extract that from the multipart request and print it out:
Logger.info(request.body.dataParts.get("text").get)
I get:
Med V?nliga H?lsningar Jakobs Webshop
Ok so with the given info from Sendgrid let's fix the string so that it is UTF-8.
def parseMail = Action(parse.multipartFormData) {
request => {
val inputBuffer = request.body.dataParts.get("text").map {
v => ByteBuffer.wrap(v.head.getBytes())
}
val fromCharset = Charset.forName("ISO-8859-1")
val toCharset = Charset.forName("UTF-8")
val data = fromCharset.decode(inputBuffer.get)
Logger.info(""+data)
val outputBuffer = toCharset.encode(data)
val text = new String(outputBuffer.array())
// Save stuff to MongoDB instance
}
This results in:
Med V�nliga H�lsningar Jakobs Webshop
So this is very strange. This should work.
I wonder what actually happens in the body parser parse.multipartFormData and the datapart handler:
def handleDataPart: PartHandler[Part] = {
case headers # PartInfoMatcher(partName) if !FileInfoMatcher.unapply(headers).isDefined =>
Traversable.takeUpTo[Array[Byte]](DEFAULT_MAX_TEXT_LENGTH)
.transform(Iteratee.consume[Array[Byte]]().map(bytes => DataPart(partName, new String(bytes, "utf-8")))(play.core.Execution.internalContext))
.flatMap { data =>
Cont({
case Input.El(_) => Done(MaxDataPartSizeExceeded(partName), Input.Empty)
case in => Done(data, in)
})
}(play.core.Execution.internalContext)
}
When consuming the data a new String is created with the encoding utf-8:
.transform(Iteratee.consume[Array[Byte]]().map(bytes => DataPart(partName, new String(bytes, "utf-8")))(play.core.Execution.internalContext))
Does this mean that my ISO-8859-1 encoded string text is encoded with utf-8 when parsed? If so, how should I create my parser to decode and then encode my params according to the provided JSON object charsets?
Clearly I'm doing something wrong but I can't figure it out!
You'll need to copy the implementation of the parse.multipartFormData function, changing the decodings from utf-8 to iso-8859-1, and use that in your Action.
The problem is that play decodes everything with UTF-8 by default, and there is no way to change that, other than implementing your own parser.
Have you tried changing the default encoding to UTF-8?
See this question for details: Printing Unicode from Scala interpreter