PageRenderTime 76ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/app/models/UrlShortener.scala

https://github.com/ufholdpig/hstest
Scala | 153 lines | 57 code | 19 blank | 77 comment | 2 complexity | 4045432cbf7594ba4f8ea29fa568b8b5 MD5 | raw file
  1. package models
  2. import java.security.MessageDigest
  3. import com.mongodb.casbah.Imports._
  4. import play.api.libs.json._
  5. //import scala.math.BigInt // Improvement for LONG number within MongoDB
  6. abstract class URLSTR(url: String)
  7. case class LURL(url: String) extends URLSTR(url)
  8. case class SURL(url: String) extends URLSTR(url)
  9. /**
  10. * Main Object which supplied long and short URL converting
  11. */
  12. object UrlShortener {
  13. /*
  14. * MongoDB database access address.
  15. * MongoHQ and MongoLab
  16. */
  17. //dharma.mongohq.com:10043/app17027224
  18. //ds035428.mongolab.com:35428/hootsuite
  19. val uri = MongoClientURI("mongodb://allen:allen88@ds035428.mongolab.com:35428/hootsuite")
  20. val co = MongoClient(uri)("hootsuite")("hootsuite")
  21. /*
  22. * Local database for simple access
  23. */
  24. //val mongoClient = MongoClient("localhost", 27017)
  25. //val co = mongoClient("hootsuite")("hootsuite")
  26. /**
  27. * Method toShortUrl(s: String): String
  28. * Input: Long URL
  29. * Output: Shortened RUL
  30. *
  31. * Description:
  32. * Convert to short URL by giving long URL. Check from database if:
  33. * 1: Exist, get the corresponding short URL
  34. * 2: Non-Exist: convert to short one and append to database
  35. */
  36. def toShortUrl(s: String): String = {
  37. val rc = checkUrl(LURL(s))
  38. rc match {
  39. case None => calShortUrl(s)
  40. case _ => rc.get.getAs[String]("shortUrl").get
  41. }
  42. }
  43. /**
  44. * Method toLongUrl(s: String): Option[String]
  45. * Input: Short URL
  46. * Output: Option[String] for long RUL
  47. *
  48. * Description:
  49. * Check from database if:
  50. * 1: Exist, get the corresponding long URL
  51. * 2: Non-Exist: return None
  52. * 3: return Options to caller for re-directing to new page
  53. */
  54. def toLongUrl(s: String): Option[String] = {
  55. val rc = checkUrl(SURL(s))
  56. rc match {
  57. case None => None
  58. case _ => //val n = rc.get.getAs[Long]("count").get + 1
  59. //co.update( MongoDBObject("shortUrl" -> s),
  60. // MongoDBObject("$set" -> MongoDBObject("count" -> n))
  61. // )
  62. //------------------------------------------------------------------
  63. // Use findAndModify to increase count field to avoid parallel issue
  64. //------------------------------------------------------------------
  65. co.findAndModify(query = MongoDBObject("shortUrl" -> s),
  66. update = $inc("count" -> 1)
  67. )
  68. rc.get.getAs[String]("longUrl")
  69. }
  70. }
  71. /**
  72. * Method checkStatus(s: String): String
  73. * Input: Short URL | Long URL | ALL
  74. * Output: Detail information about mapping of long and short URL, include count
  75. *
  76. * Description:
  77. * Check from database if:
  78. * 1: Exist, get the informations for reporting
  79. * 2: Non-Exist: return None
  80. */
  81. def checkStatus(s: String): String = {
  82. val query = s match {
  83. case "ALL" => null
  84. case x if(x.length == 6) => MongoDBObject("shortUrl" -> s)
  85. case _ => MongoDBObject("longUrl" -> s)
  86. }
  87. //"[%s]".format(co.find(query,MongoDBObject("_id" -> 0)).toList.mkString(","))
  88. co.find(query,MongoDBObject("_id" -> 0)).toList.mkString(",")
  89. }
  90. /*
  91. * Assistant method to check URL from database
  92. */
  93. def checkUrl(u: URLSTR): Option[models.UrlShortener.co.T] = {
  94. val query = u match {
  95. case LURL(s) => MongoDBObject("longUrl" -> s)
  96. case SURL(s) => MongoDBObject("shortUrl" -> s)
  97. //case _ => null
  98. }
  99. co.findOne(query)
  100. }
  101. /*
  102. * Long URL shortener.
  103. *
  104. * Method calShortUrl(s: String): String
  105. * Input: Long URL
  106. * Output: short URL
  107. *
  108. * Description:
  109. * Calculate URL string to generate hash code. Reform to a unique short code.
  110. * Depends on MD5 hash code, get 4 6-characters code as short URL
  111. * Need improve algorithm to ensure short code collisions.
  112. */
  113. val dict = ('a' to 'z') ++ ('0' to '9') ++ ('A' to 'Z')
  114. def calShortUrl(s: String): String = {
  115. /*
  116. * Will consider SHA1 (20 bytes), SHA256 or SHA512, up to 40 bytes hash code,
  117. * MD5 has 16 bytes
  118. */
  119. val keyStr = MessageDigest.getInstance("MD5")
  120. keyStr.reset()
  121. keyStr.update(s.getBytes)
  122. val urlList = keyStr.digest().sliding(4,4).map { x =>
  123. val m = ((x.toList.head & 0x3F) :: x.toList.tail).map("%02x".format(_)).mkString
  124. val n = Integer.parseInt(m, 16)
  125. (0 to 5).map { i => dict((n >> i*5) & 0x3d) }.mkString
  126. }.toList
  127. /*
  128. * Will consider the short url collision, and pick another one
  129. * but Base62(a-zA-Z0-9) for 6 characters giving us 62 ^ 6 short urls
  130. */
  131. co.save( MongoDBObject("shortUrl" -> urlList.head, "longUrl" -> s, "count" -> 1.toLong) )
  132. urlList.head
  133. }
  134. }