All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.relaysoft.testing.azure.blob.mock.providers.InMemoryProvider.scala Maven / Gradle / Ivy

The newest version!
package net.relaysoft.testing.azure.blob.mock.providers

import akka.http.scaladsl.model.DateTime
import com.typesafe.scalalogging.LazyLogging
import net.relaysoft.testing.azure.blob.mock.exceptions._
import net.relaysoft.testing.azure.blob.mock.models.{Blob, Container}
import net.relaysoft.testing.azure.blob.mock.responses._
import net.relaysoft.testing.azure.blob.mock.utils.HeaderNames
import org.apache.commons.codec.digest.DigestUtils

import scala.collection.concurrent.TrieMap
import scala.collection.mutable

class InMemoryProvider(port:Int, accountName:String) extends Provider with LazyLogging {

  private val containerDataStore = new TrieMap[String, ContainerContents]

  private case class ContainerContents(container: Container, blobs: mutable.Map[String, Blob])

  override def clear(): Unit = {
    logger.debug(s"account=$accountName, message=Clearing all blobs and containers from the storage account")
    containerDataStore.clear()
  }

  override def deleteBlob(containerName: String, blobName: String): Unit = {
    logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, message=Deleting blob.")
    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        containerContent.blobs.get(blobName) match {
          case Some(blob) =>
            containerContent.blobs.remove(blob.name)
          case None => throw BlobNotFoundException()
        }
      case None => throw ContainerNotFoundException()
    }
  }

  override def deleteContainer(containerName: String): Unit = {
    logger.debug(s"account=$accountName, container=$containerName, message=Deleting container.")
    containerDataStore.get(containerName) match {
      case Some(_) => containerDataStore.remove(containerName)
      case None => throw ContainerNotFoundException()
    }
  }

  override def createContainer(containerName: String): Container = {
    logger.debug(s"account=$accountName, container=$containerName, message=Creating new storage data container.")
    if(containerDataStore.contains(containerName)){
      throw ContainerAlreadyExistsException()
    }
    val lastModifiedDateTime = DateTime.now
    val container = Container(containerName, lastModifiedDateTime, getMD5Hash(lastModifiedDateTime))
    val containerContent = ContainerContents(container, new TrieMap)
    containerDataStore.put(containerName, containerContent)
    container
  }

  override def getBlob(containerName: String, blobName: String, headers: Map[String, String]): Blob = {
    logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, message=Getting blob")
    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        containerContent.blobs.get(blobName) match {
          case Some(blob) =>
            blob
          case None => throw BlobNotFoundException()
        }
      case None => throw ContainerNotFoundException()
    }
  }

  override def getContainerProperties(containerName: String): Container = {
    logger.debug(s"account=$accountName, container=$containerName, message=Getting properties for the storage data container.")
    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        containerContent.container
      case None => throw ContainerNotFoundException()
    }
  }

  override def listBlobs(containerName: String, params: Map[String, String]): ListBlobsResponse = {
    logger.debug(s"account=$accountName, container=$containerName, message=Listing blobs from container.")
    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        val prefix = params.getOrElse("prefix", "")
        if(prefix.nonEmpty)
          logger.debug(s"account=$accountName, container=$containerName, message=Filtering blobs with prefix: $prefix")
        val matchingBlobContents = containerContent.blobs.view.filterKeys(_.startsWith(prefix))
        val matchingBlobs = matchingBlobContents map {
          case (name, blob) =>
            logger.debug(s"account=$accountName, container=$containerName, message=Found blob: $name")
            blob
        }
        logger.debug(s"account=$accountName, container=$containerName, message=Listing blobs: ${matchingBlobs.map(_.name).toList}")
        ListBlobsResponse(getBlobBaseUrl(containerName), matchingBlobs.toList.distinct)
      case None => throw ContainerNotFoundException()
    }
  }

  override def listContainers(params: Map[String, String]): ListContainersResponse = {
    logger.debug(s"account=$accountName, message=Listing containers from storage account.")
    val prefix = params.getOrElse("prefix", "")
    if(prefix.nonEmpty)
      logger.debug(s"account=$accountName, message=Filtering containers with name prefix: $prefix")
    val matchingContainerContainerContents = containerDataStore.values
      .filter(_.container.name.startsWith(prefix))
      .toList
    val matchingContainers = matchingContainerContainerContents map { contents =>
      logger.debug(s"account=$accountName, message=Found container: ${contents.container.name}")
      contents.container
    }
    logger.debug(s"account=$accountName, message=listing containers: $matchingContainers")
    ListContainersResponse(getContainerBaseUrl, matchingContainers, prefix)
  }

  override def putBlob(containerName: String, blobName:String, data:Array[Byte],
                       params: Map[String, String] = new TrieMap[String, String]().toMap): Blob = {
    logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, " +
      s"message=Saving blob to container")
    if(containerName == null || containerName.isEmpty || blobName == null || blobName.isEmpty || data == null)
      throw InvalidBlobOrBlockException()
    val paramString = params.map(_.productIterator.mkString(":")).mkString(",")
    logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, " +
      s"message=With parameters: $paramString")
    val contentLength = params.getOrElse(HeaderNames.CONTENT_LENGTH, Integer.toString(data.length))
    val contentType = params.getOrElse(HeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8")
    val contentEncoding = params.getOrElse(HeaderNames.CONTENT_ENCODING, "")
    val contentLanguage = params.getOrElse(HeaderNames.CONTENT_LANGUAGE, "")

    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        containerContent.blobs.get(blobName) match {
          case Some(blob) =>
            logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, message=Update existing blob")
            val metaData = getMetaData(params)
            val newBlob = blob.copy(
              lastModifiedDate = DateTime.now,
              etag = getMD5Hash(DateTime.now),
              contentLength = contentLength.toLong,
              contentType = contentType,
              contentEncoding = contentEncoding,
              contentLanguage = contentLanguage,
              contentMD5 = getMD5Hash(data),
              metaData = if(metaData.isEmpty) blob.metaData else metaData,
              content = data
            )
            containerContent.blobs.put(blobName, newBlob)
            newBlob
          case None =>
            logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, message=Create new blob")
            val metaData = getMetaData(params)
            val newBlob = Blob(name = blobName,
              lastModifiedDate = DateTime.now,
              etag = getMD5Hash(DateTime.now),
              contentLength = contentLength.toLong,
              contentType = contentType,
              contentEncoding = contentEncoding,
              contentLanguage = contentLanguage,
              contentMD5 = getMD5Hash(data),
              metaData = if(metaData.isEmpty) new TrieMap[String, String]().toMap else metaData,
              content = data
            )
            containerContent.blobs.put(blobName, newBlob)
            newBlob
        }
      case None => throw ContainerNotFoundException()
    }
  }

  override def setBlobMetadata(containerName:String, blobName:String, params: Map[String, String]): Blob = {
    logger.debug(s"account=$accountName, container=$containerName, blob=$blobName, message=Setting blob meta-data ")
    if(containerName == null || containerName.isEmpty || blobName == null || blobName.isEmpty)
      throw InvalidBlobOrBlockException()
    containerDataStore.get(containerName) match {
      case Some(containerContent) =>
        containerContent.blobs.get(blobName) match {
          case Some(blob) =>
            val metaData = getMetaData(params)
            val newBlob = blob.copy(
              lastModifiedDate = DateTime.now,
              etag = getMD5Hash(DateTime.now),
              metaData = metaData
            )
            containerContent.blobs.put(blobName, newBlob)
            newBlob
          case None => throw BlobNotFoundException()
        }
      case None => throw ContainerNotFoundException()
    }
  }


  /**
   * Get base URL for the blob.
   *
   * @param container Data storage container name
   * @return Base URL for the blob.
   */
  private def getBlobBaseUrl(container:String): String = {
    s"http://localhost:$port/$accountName/$container"
  }

  /**
   * Get base URL for the storage data container.
   *
   * @return Base URL for the container.
   */
  private def getContainerBaseUrl: String = {
    s"http://localhost:$port/$accountName"
  }

  /**
   * Get meta-data key value pairs from request params.
   *
   * @param params Request params
   * @return Meta data key value pairs or empty map if there are none.
   */
  private def getMetaData(params: Map[String, String]): Map[String, String] = {
    val metaData = new TrieMap[String, String]
    val matchingParams = params.keys.filter(_.startsWith(HeaderNames.PREFIX_X_MS_META))
    matchingParams map { key =>
      metaData.put(key.replace(HeaderNames.PREFIX_X_MS_META, ""), params.getOrElse(key, ""))
    }
    metaData.toMap
  }

  /**
   * Create MD5 hash from the given date time.
   *
   * @param dateTime Date time to get hash for
   * @return MD5 hash string
   */
  private def getMD5Hash(dateTime:DateTime): String = {
    DigestUtils.md5Hex(dateTime.toString().getBytes)
  }

  /**
   * Create MD5 hash from the given data content.
   *
   * @param data Data content to get hash for
   * @return MD5 hash string
   */
  private def getMD5Hash(data:Array[Byte]): String = {
    DigestUtils.md5Hex(data)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy