Support real time unavailable item constraint
diff --git a/README.md b/README.md
index d2ed8ce..3595257 100644
--- a/README.md
+++ b/README.md
@@ -22,16 +22,19 @@
 normal:
 
 ```
-curl -H "Content-Type: application/json" \
+$ curl -H "Content-Type: application/json" \
 -d '{
   "user" : "u1",
   "num" : 10 }' \
 http://localhost:8000/queries.json \
 -w %{time_connect}:%{time_starttransfer}:%{time_total}
+
+
+{"itemScores":[{"item":"i43","score":0.9987465791873191},{"item":"i21","score":0.9987465791873191},{"item":"i20","score":0.997849496769407},{"item":"i49","score":0.9969960024975738},{"item":"i41","score":0.9955226345451645},{"item":"i37","score":0.9953403563456491},{"item":"i8","score":0.9944547925923641},{"item":"i46","score":0.9928301245473273},{"item":"i1","score":0.9910679754780729},{"item":"i18","score":0.9903295848514115}]}
 ```
 
 ```
-curl -H "Content-Type: application/json" \
+$ curl -H "Content-Type: application/json" \
 -d '{
   "user" : "u1",
   "num": 10,
@@ -113,3 +116,37 @@
 }'
 
 ```
+
+## handle unavailable items
+
+Set the following items "unavailable" (need to specify complete list each time when this list is changed):
+
+```
+curl -i -X POST http://localhost:7070/events.json?accessKey=s49uNadpNh5C4cRGCCIp8NqK1UWkaQPsN1SN2y670pZfRVXsbUepzmZQmyfjWeHo \
+-H "Content-Type: application/json" \
+-d '{
+  "event" : "$set",
+  "entityType" : "itemConstraint"
+  "entityId" : "unavailable",
+  "properties" : {
+    "items": ["i43", "i20", "i37", "i3", "i4", "i5"],
+  }
+  "eventTime" : "2015-02-17T02:11:21.934Z"
+}'
+```
+
+No more items "unavailable":
+
+```
+curl -i -X POST http://localhost:7070/events.json?accessKey=s49uNadpNh5C4cRGCCIp8NqK1UWkaQPsN1SN2y670pZfRVXsbUepzmZQmyfjWeHo \
+-H "Content-Type: application/json" \
+-d '{
+  "event" : "$set",
+  "entityType" : "itemConstraint"
+  "entityId" : "unavailable",
+  "properties" : {
+    "items": [],
+  }
+  "eventTime" : "2015-02-18T02:11:21.934Z"
+}'
+```
diff --git a/engine.json b/engine.json
index 5748efa..a8f5576 100644
--- a/engine.json
+++ b/engine.json
@@ -4,7 +4,7 @@
   "engineFactory": "org.template.ecommercerecommendation.ECommerceRecommendationEngine",
   "datasource": {
     "params" : {
-      "appId": 16
+      "appId": 17
     }
   },
   "algorithms": [
diff --git a/src/main/scala/ALSAlgorithm.scala b/src/main/scala/ALSAlgorithm.scala
index ea50e62..cd424fe 100644
--- a/src/main/scala/ALSAlgorithm.scala
+++ b/src/main/scala/ALSAlgorithm.scala
@@ -57,6 +57,7 @@
   extends P2LAlgorithm[PreparedData, ALSModel, Query, PredictedResult] {
 
   @transient lazy val logger = Logger[this.type]
+  // NOTE: use getLEvents() for local access
   @transient lazy val lEventsDb = Storage.getLEvents()
 
   def train(data: PreparedData): ALSModel = {
@@ -94,7 +95,8 @@
       }.filter { case ((u, i), v) =>
         // keep events with valid user and item index
         (u != -1) && (i != -1)
-      }.reduceByKey(_ + _) // aggregate all view events of same user-item pair
+      }
+      .reduceByKey(_ + _) // aggregate all view events of same user-item pair
       .map { case ((u, i), v) =>
         // MLlibRating requires integer index for user and item
         MLlibRating(u, i, v)
@@ -173,7 +175,7 @@
           event.targetEntityId.get
         } catch {
           case e => {
-            logger.error("Can't get targetEntityId of event ${event}.")
+            logger.error(s"Can't get targetEntityId of event ${event}.")
             throw e
           }
         }
@@ -182,10 +184,34 @@
       Set[String]()
     }
 
-    // combine query's blackList and seenItems into final blackList
+    val unavailableItems: Set[String] = lEventsDb.find(
+      appId = ap.appId,
+      // entityType and entityId is specified for fast lookup
+      entityType = Some("itemConstraint"),
+      entityId = Some("unavailable"),
+      eventNames = Some(Seq("$set")),
+      limit = Some(1),
+      reversed = Some(true),
+      timeout = Duration(200, "millis")
+    ) match {
+      case Right(x) => {
+        if (x.hasNext) {
+          x.next.properties.get[Set[String]]("items")
+        } else {
+          Set[String]()
+        }
+      }
+      case Left(e) => {
+        logger.error(s"Error when read unavailable items: ${e}")
+        Set[String]()
+      }
+    }
+
+    // combine query's blackList,seenItems and unavailableItems
+    // into final blackList.
     // convert seen Items list from String ID to interger Index
-    val finalBlackList: Set[Int] = (blackList ++ seenItems).map( x =>
-      model.itemStringIntMap.get(x)).flatten
+    val finalBlackList: Set[Int] = (blackList ++ seenItems ++
+      unavailableItems).map( x => model.itemStringIntMap.get(x)).flatten
 
     val userFeature =
       model.userStringIntMap.get(query.user).map { userIndex =>
@@ -225,8 +251,7 @@
 
     } else {
       // the user doesn't have feature vector.
-      // For example, new user created after model is trained.
-
+      // For example, new user is created after model is trained.
       logger.info(s"No userFeature found for user ${query.user}.")
       predictNewUser(
         model = model,