Skip to content
This repository has been archived by the owner on Jan 21, 2022. It is now read-only.

Commit

Permalink
Backport casbah-gridfs from 3.0
Browse files Browse the repository at this point in the history
- Fixes SCALA-45: Allow filename and contentType to be nullable
  * Retrieving filename or contentType on a GridFS File now returns
    Option[String] when fetched
  * To facilitate sane usage, the loan-pattern/execute-around-resource
    methods now return the _id of the created file as Option[AnyRef]
  • Loading branch information
Brendan W. McAdams committed May 16, 2012
1 parent fcdabf3 commit 3579826
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 44 deletions.
80 changes: 61 additions & 19 deletions casbah-gridfs/src/main/scala/GridFS.scala
Expand Up @@ -71,16 +71,13 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
implicit val db = underlying.getDB().asScala

def iterator = new Iterator[GridFSDBFile] {
val fileSet = files.underlying
val fileSet = files
def count() = fileSet.count
def itcount() = fileSet.itcount()
def jIterator() = fileSet.iterator asScala
override def length() = fileSet.length
def numGetMores() = fileSet.numGetMores
def numSeen() = fileSet.numSeen
def remove() = fileSet.remove

def curr() = new GridFSDBFile(fileSet.curr.asInstanceOf[MongoGridFSDBFile])
def curr() = new GridFSDBFile(fileSet.next().asInstanceOf[MongoGridFSDBFile])
def explain() = fileSet.explain

def next() = new GridFSDBFile(fileSet.next.asInstanceOf[MongoGridFSDBFile])
Expand All @@ -96,8 +93,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* a code block.
*
*/
protected[gridfs] def loan[T <: GridFSFile](file: T)(op: T => Unit) = op(file)

protected[gridfs] def loan[T <: GridFSFile](file: T)(op: T => Option[AnyRef]) = op(file)
/**
* Create a new GridFS File from a scala.io.Source
*
Expand All @@ -106,6 +102,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* It AUTOMATICALLY saves the GridFS file at it's end, so throw an exception if you want to fail.
* If you don't want automatic saving/loaning please see the createFile method instead.
* @see createFile
* @returns The ID of the created File (Option[AnyRef])
*/
def apply(data: scala.io.Source)(op: FileWriteOp) = withNewFile(data)(op)

Expand All @@ -117,6 +114,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* It AUTOMATICALLY saves the GridFS file at it's end, so throw an exception if you want to fail.
* If you don't want automatic saving/loaning please see the createFile method instead.
* @see createFile
* @returns The ID of the created File (Option[AnyRef])
*/
def apply(data: Array[Byte])(op: FileWriteOp) = withNewFile(data)(op)

Expand All @@ -128,6 +126,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* It AUTOMATICALLY saves the GridFS file at it's end, so throw an exception if you want to fail.
* If you don't want automatic saving/loaning please see the createFile method instead.
* @see createFile
* @returns The ID of the created File (Option[AnyRef])
*/
def apply(f: File)(op: FileWriteOp) = withNewFile(f)(op)

Expand All @@ -139,6 +138,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* It AUTOMATICALLY saves the GridFS file at it's end, so throw an exception if you want to fail.
* If you don't want automatic saving/loaning please see the createFile method instead.
* @see createFile
* @returns The ID of the created File (Option[AnyRef])
*/
def apply(in: InputStream)(op: FileWriteOp) = withNewFile(in)(op)

Expand All @@ -150,6 +150,7 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
* It AUTOMATICALLY saves the GridFS file at it's end, so throw an exception if you want to fail.
* If you don't want automatic saving/loaning please see the createFile method instead.
* @see createFile
* @returns The ID of the created File (Option[AnyRef])
*/
def apply(in: InputStream, filename: String)(op: FileWriteOp) = withNewFile(in, filename)(op)

Expand All @@ -162,14 +163,56 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
*/
def createFile(data: scala.io.Source): GridFSInputFile = throw new UnsupportedOperationException("Currently no support for scala.io.Source")
def withNewFile(data: scala.io.Source)(op: FileWriteOp) = throw new UnsupportedOperationException("Currently no support for scala.io.Source")

def createFile(data: Array[Byte]): GridFSInputFile = underlying.createFile(data)
def withNewFile(data: Array[Byte])(op: FileWriteOp) { loan(createFile(data))({ fh => op(fh); fh.save }) }
/**
* Loan pattern style file creation.
* @returns The ID of the created File (Option[AnyRef])
*/
def withNewFile(data: Array[Byte])(op: FileWriteOp) = loan(createFile(data)) { fh =>
op(fh)
fh.save()
fh.validate()
Option(fh.id)
}

def createFile(f: File): GridFSInputFile = underlying.createFile(f)
def withNewFile(f: File)(op: FileWriteOp) { loan(createFile(f))({ fh => op(fh); fh.save }) }
/**
* Loan pattern style file creation.
* @returns The ID of the created File (Option[AnyRef])
*/
def withNewFile(f: File)(op: FileWriteOp) = loan(createFile(f)) { fh =>
op(fh)
fh.save()
fh.validate()
Option(fh.id)
}

def createFile(in: InputStream): GridFSInputFile = underlying.createFile(in)
def withNewFile(in: InputStream)(op: FileWriteOp) { loan(createFile(in))({ fh => op(fh); fh.save }) }

/**
* Loan pattern style file creation.
* @returns The ID of the created File (Option[AnyRef])
*/
def withNewFile(in: InputStream)(op: FileWriteOp) = loan(createFile(in)) { fh =>
op(fh)
fh.save()
fh.validate()
Option(fh.id)
}

def createFile(in: InputStream, filename: String): GridFSInputFile = underlying.createFile(in, filename)
def withNewFile(in: InputStream, filename: String)(op: FileWriteOp) { loan(createFile(in, filename))({ fh => op(fh); fh.save }) }
/**
* Loan pattern style file creation.
* @returns The ID of the created File (Option[AnyRef])
*/
def withNewFile(in: InputStream, filename: String)(op: FileWriteOp) = loan(createFile(in, filename)) { fh =>
op(fh)
fh.save()
fh.validate()
Option(fh.id)
}


/** Hacky fun -
* Unload the Joda Time code, IF ITS LOADED, and reload it after we're done.
Expand Down Expand Up @@ -233,28 +276,27 @@ class GridFS protected[gridfs] (val underlying: MongoGridFS) extends Iterable[Gr
}

@BeanInfo
trait GridFSFile extends MongoDBObject with Logging {
val underlying: MongoGridFSFile
class GridFSFile(override val underlying: MongoGridFSFile) extends MongoDBObject with Logging {

override def iterator = underlying.keySet.asScala.map { k =>
k -> underlying.get(k)
}.toMap.iterator.asInstanceOf[Iterator[(String, AnyRef)]]

def save = underlying.save
def save() { underlying.save() }

/**
* validate the object.
* Throws an exception if it fails
* @throws MongoException An error describing the validation failure
*/
def validate = underlying.validate
def validate() { underlying.validate() }

def numChunks: Int = underlying.numChunks

def id = underlying.getId
def filename = underlying.getFilename
def filename = Option(underlying.getFilename)
// todo - does mongo support mime magic? Should we pull some in here?
def contentType = underlying.getContentType
def contentType = Option(underlying.getContentType)
def length = underlying.getLength
def chunkSize = underlying.getChunkSize
def uploadDate = underlying.getUploadDate
Expand All @@ -269,7 +311,7 @@ trait GridFSFile extends MongoDBObject with Logging {
}

@BeanInfo
class GridFSDBFile protected[gridfs] (override val underlying: MongoGridFSDBFile) extends GridFSFile {
class GridFSDBFile protected[gridfs] (override val underlying: MongoGridFSDBFile) extends GridFSFile(underlying) {

def inputStream = underlying.getInputStream

Expand All @@ -286,7 +328,7 @@ class GridFSDBFile protected[gridfs] (override val underlying: MongoGridFSDBFile
}

@BeanInfo
class GridFSInputFile protected[gridfs] (override val underlying: MongoGridFSInputFile) extends GridFSFile {
class GridFSInputFile protected[gridfs] (override val underlying: MongoGridFSInputFile) extends GridFSFile(underlying) {
def filename_=(name: String) = underlying.setFilename(name)
def contentType_=(cT: String) = underlying.setContentType(cT)
}
Expand Down
115 changes: 90 additions & 25 deletions casbah-gridfs/src/test/scala/GridFSSpec.scala
Expand Up @@ -20,48 +20,67 @@
*
*/

package com.mongodb.casbah
package test
package com.mongodb.casbah.test.gridfs

import com.mongodb.casbah.Imports._
import com.mongodb.casbah.gridfs.Imports._
import com.mongodb.casbah.commons.Logging

import java.security.MessageDigest
import java.io._

import org.specs._
import org.specs.specification.PendingUntilFixed
class GridFSSpec extends com.mongodb.casbah.commons.test.CasbahMutableSpecification {
implicit val mongo = MongoConnection()("casbah_test")
mongo.dropDatabase()
val gridfs = GridFS(mongo)
def logo_fh = new FileInputStream("casbah-gridfs/src/test/resources/powered_by_mongo.png")

class GridFSSpec extends Specification with PendingUntilFixed with Logging {
val logo_md5 = "479977b85391a88bbc1da1e9f5175239"
val digest = MessageDigest.getInstance("MD5")
def logo_bytes = {
val data = new Array[Byte](logo_fh.available())
logo_fh.read(data)
data
}

def logo = new ByteArrayInputStream(logo_bytes)

lazy val digest = MessageDigest.getInstance("MD5")
digest.update(logo_bytes)
lazy val logo_md5 = digest.digest().map("%02X" format _).mkString.toLowerCase()

def findItem(id: ObjectId, filename: Option[String] = None, contentType: Option[String] = None) = {
gridfs.findOne(id) must beSome[GridFSDBFile]
var md5 = ""
var fn: Option[String] = None
var ct: Option[String] = None
gridfs.findOne(id) foreach { file =>
md5 = file.md5
fn = file.filename
ct = file.contentType
}

md5 must beEqualTo(logo_md5)
fn must beEqualTo(filename)
ct must beEqualTo(contentType)
}

"Casbah's GridFS Implementations" should {
shareVariables()
implicit val mongo = MongoConnection()("casbah_test")
mongo.dropDatabase()
val logo = new FileInputStream("casbah-gridfs/src/test/resources/powered_by_mongo.png")
val gridfs = GridFS(mongo)

"Correctly save a file to GridFS" in {
gridfs must notBeNull
logo must notBeNull

gridfs(logo) { fh =>
fh.filename = "powered_by_mongo.png"
"Find the file in GridFS later" in {
val id = gridfs(logo_bytes) { fh =>
fh.filename = "powered_by_mongo_find.png"
fh.contentType = "image/png"
}

}

"Find the file in GridFS later" in {
gridfs.findOne("powered_by_mongo.png") must beSome[GridFSDBFile]
gridfs.findOne("powered_by_mongo.png") foreach { file =>
file must notBeNull
file must haveSuperClass[GridFSDBFile]
file.md5 must beEqualTo(logo_md5)
gridfs.findOne("powered_by_mongo_find.png") must beSome[GridFSDBFile]
var md5 = ""
gridfs.findOne("powered_by_mongo_find.png") foreach { file =>
md5 = file.md5
log.debug("MD5: %s", file.md5)
}
md5 must beEqualTo(logo_md5)
}

"Correctly catch the non-existence of a file and fail gracefully" in {
Expand All @@ -70,17 +89,63 @@ class GridFSSpec extends Specification with PendingUntilFixed with Logging {

"Return a wrapped MongoCursor if you call files, as reported by Gregg Carrier" in {
val files = gridfs.files
files must notBeNull
files must haveClass[MongoCursor]
files must beAnInstanceOf[MongoCursor]
}

"Be properly iterable" in {
val id = gridfs(logo) { fh =>
fh.filename = "powered_by_mongo_iter.png"
fh.contentType = "image/png"
}
var x = false
for (f <- gridfs) x = true
x must beTrue
}

}
"Return the created file's ID from the loan pattern methods." should {

"Using a InputStream" in {
val id = gridfs(logo) { fh =>
fh.filename = "powered_by_mongo_inputstream.png"
fh.contentType = "image/png"
}
id must beSome[AnyRef]
id.get must beAnInstanceOf[ObjectId]
findItem(id.get.asInstanceOf[ObjectId], Some("powered_by_mongo_inputstream.png"), Some("image/png"))
}

"Using a Byte Array" in {
val id = gridfs(logo_bytes) { fh =>
fh.filename = "powered_by_mongo_bytes.png"
fh.contentType = "image/png"
}
id must beSome
id.get must beAnInstanceOf[ObjectId]
findItem(id.get.asInstanceOf[ObjectId], Some("powered_by_mongo_bytes.png"), Some("image/png"))
}
}
"Allow filename and contentType to be nullable, returning 'None' appropriately." in {
"Filename may be null" in {
val id = gridfs(logo) { fh =>
fh.contentType = "image/png"
}
id.get must beAnInstanceOf[ObjectId]
findItem(id.get.asInstanceOf[ObjectId], None, Some("image/png"))
}
"content Type may be null" in {
val id = gridfs(logo) { fh =>
fh.filename = "null_content_type.png"
}
id.get must beAnInstanceOf[ObjectId]
findItem(id.get.asInstanceOf[ObjectId], Some("null_content_type.png"), None)
}
"both may be null" in {
val id = gridfs(logo) { fh => }
id.get must beAnInstanceOf[ObjectId]
findItem(id.get.asInstanceOf[ObjectId])
}
}

}

Expand Down

0 comments on commit 3579826

Please sign in to comment.