Add item recommendation output with custom item attributes support
diff --git a/client/src/main/java/io/prediction/Client.java b/client/src/main/java/io/prediction/Client.java
index de01d8c..30bde8a 100644
--- a/client/src/main/java/io/prediction/Client.java
+++ b/client/src/main/java/io/prediction/Client.java
@@ -1,6 +1,7 @@
package io.prediction;
import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
@@ -11,6 +12,9 @@
import java.io.IOException;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
/**
@@ -22,7 +26,7 @@
* Multiple simultaneous asynchronous requests is made possible by the high performance backend provided by the <a href="https://github.com/AsyncHttpClient/async-http-client">Async Http Client</a>.
*
* @author The PredictionIO Team (<a href="http://prediction.io">http://prediction.io</a>)
- * @version 0.4
+ * @version 0.4.1
* @since 0.1
*/
public class Client {
@@ -136,7 +140,12 @@
int l = a.size();
String[] r = new String[l];
for (int i = 0; i < l; i++) {
- r[i] = a.get(i).getAsString();
+ JsonElement e = a.get(i);
+ if (e.isJsonNull()) {
+ r[i] = null;
+ } else {
+ r[i] = e.getAsString();
+ }
}
return r;
}
@@ -529,6 +538,18 @@
}
/**
+ * Get a get top-n recommendations request builder that can be used to add additional request parameters.
+ *
+ * @param engine engine name
+ * @param uid ID of the User whose recommendations will be gotten
+ * @param n number of top recommendations to get
+ * @param attributes array of item attribute names to be returned with the result
+ */
+ public ItemRecGetTopNRequestBuilder getItemRecGetTopNRequestBuilder(String engine, String uid, int n, String[] attributes) {
+ return (new ItemRecGetTopNRequestBuilder(this.apiUrl, this.apiFormat, this.appkey, engine, uid, n)).attributes(attributes);
+ }
+
+ /**
* Sends an asynchronous get recommendations request to the API.
*
* @param builder an instance of {@link ItemRecGetTopNRequestBuilder} that will be turned into a request
@@ -588,6 +609,61 @@
}
}
+ /**
+ * Sends a synchronous get recommendations request to the API.
+ *
+ * @param engine engine name
+ * @param uid ID of the User whose recommendations will be gotten
+ * @param n number of top recommendations to get
+ * @param attributes array of item attribute names to be returned with the result
+ *
+ * @throws ExecutionException indicates an error in the HTTP backend
+ * @throws InterruptedException indicates an interruption during the HTTP operation
+ * @throws IOException indicates an error from the API response
+ */
+ public Map<String, String[]> getItemRecTopNWithAttributes(String engine, String uid, int n, String[] attributes) throws ExecutionException, InterruptedException, IOException {
+ return this.getItemRecTopNWithAttributes(this.getItemRecTopNAsFuture(this.getItemRecGetTopNRequestBuilder(engine, uid, n, attributes)));
+ }
+
+ /**
+ * Sends a synchronous get recommendations request to the API.
+ *
+ * @param builder an instance of {@link ItemRecGetTopNRequestBuilder} that will be turned into a request
+ *
+ * @throws ExecutionException indicates an error in the HTTP backend
+ * @throws InterruptedException indicates an interruption during the HTTP operation
+ * @throws IOException indicates an error from the API response
+ */
+ public Map<String, String[]> getItemRecTopNWithAttributes(ItemRecGetTopNRequestBuilder builder) throws ExecutionException, InterruptedException, IOException {
+ return this.getItemRecTopNWithAttributes(this.getItemRecTopNAsFuture(builder));
+ }
+
+ /**
+ * Synchronize a previously sent asynchronous get recommendations request.
+ *
+ * @param response an instance of {@link FutureAPIResponse} returned from {@link Client#getItemRecTopNAsFuture}
+ *
+ * @throws ExecutionException indicates an error in the HTTP backend
+ * @throws InterruptedException indicates an interruption during the HTTP operation
+ * @throws IOException indicates an error from the API response
+ */
+ public Map<String, String[]> getItemRecTopNWithAttributes(FutureAPIResponse response) throws ExecutionException, InterruptedException, IOException {
+ // Do not use getStatus/getMessage directly as they do not pass exceptions
+ int status = response.get().getStatus();
+ String message = response.get().getMessage();
+
+ if (status == Client.HTTP_OK) {
+ HashMap<String, String[]> results = new HashMap();
+ JsonObject messageAsJson = (JsonObject) parser.parse(message);
+ for (Map.Entry<String, JsonElement> member : messageAsJson.entrySet()) {
+ results.put(member.getKey(), this.jsonArrayAsStringArray(member.getValue().getAsJsonArray()));
+ }
+ return results;
+ } else {
+ throw new IOException(message);
+ }
+ }
+
private void userActionItem(FutureAPIResponse response) throws ExecutionException, InterruptedException, IOException {
int status = response.get().getStatus();
String message = response.get().getMessage();
diff --git a/examples/androidclient/ivy.xml b/examples/androidclient/ivy.xml
index 30cc2d3..68c9fb5 100644
--- a/examples/androidclient/ivy.xml
+++ b/examples/androidclient/ivy.xml
@@ -17,7 +17,7 @@
<dependency
name="client"
org="io.prediction"
- rev="0.4" />
+ rev="0.4.1" />
</dependencies>
</ivy-module>
\ No newline at end of file
diff --git a/examples/androidclient/res/layout/activity_main.xml b/examples/androidclient/res/layout/activity_main.xml
index bbfc428..d2ed64b 100644
--- a/examples/androidclient/res/layout/activity_main.xml
+++ b/examples/androidclient/res/layout/activity_main.xml
@@ -11,7 +11,9 @@
android:id="@+id/app_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/app_key" />
+ android:hint="@string/app_key"
+ android:lines="1"
+ android:singleLine="true" />
<LinearLayout
android:layout_width="match_parent"
@@ -23,7 +25,9 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/api_url"
- android:inputType="textUri" />
+ android:inputType="textUri"
+ android:lines="1"
+ android:singleLine="true" />
<Button
android:layout_width="wrap_content"
@@ -32,6 +36,11 @@
android:text="@string/button_get_status" />
</LinearLayout>
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#80000000" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
@@ -41,21 +50,41 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:hint="@string/engine" />
+ android:hint="@string/engine"
+ android:lines="1"
+ android:singleLine="true" />
<EditText
android:id="@+id/uid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:hint="@string/uid" />
+ android:hint="@string/uid"
+ android:lines="1"
+ android:singleLine="true" />
<EditText
android:id="@+id/n"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/n"
- android:inputType="number" />
+ android:inputType="number"
+ android:lines="1"
+ android:singleLine="true" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <EditText
+ android:id="@+id/attributes"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:hint="@string/attributes"
+ android:lines="1"
+ android:singleLine="true" />
<Button
android:layout_width="wrap_content"
@@ -64,6 +93,11 @@
android:text="@string/button_get_recs" />
</LinearLayout>
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#80000000" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
@@ -73,14 +107,18 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:hint="@string/uid" />
+ android:hint="@string/uid"
+ android:lines="1"
+ android:singleLine="true" />
<EditText
android:id="@+id/view_iid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:hint="@string/iid" />
+ android:hint="@string/iid"
+ android:lines="1"
+ android:singleLine="true" />
<Button
android:layout_width="wrap_content"
@@ -89,6 +127,11 @@
android:text="@string/button_save_view" />
</LinearLayout>
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#80000000" />
+
<TextView
android:id="@+id/openudid"
android:layout_width="match_parent"
@@ -96,6 +139,11 @@
android:hint="@string/openudid"
android:textIsSelectable="true" />
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#80000000" />
+
<TextView
android:id="@+id/console_output"
android:layout_width="match_parent"
diff --git a/examples/androidclient/res/values/strings.xml b/examples/androidclient/res/values/strings.xml
index bc2872f..7235d6c 100644
--- a/examples/androidclient/res/values/strings.xml
+++ b/examples/androidclient/res/values/strings.xml
@@ -12,6 +12,7 @@
<string name="uid">UID</string>
<string name="iid">IID</string>
<string name="n">#</string>
+ <string name="attributes">Item Attributes</string>
<string name="openudid">OpenUDID</string>
</resources>
\ No newline at end of file
diff --git a/examples/androidclient/src/io/prediction/samples/androidclient/MainActivity.java b/examples/androidclient/src/io/prediction/samples/androidclient/MainActivity.java
index 9c4a391..cc67a76 100644
--- a/examples/androidclient/src/io/prediction/samples/androidclient/MainActivity.java
+++ b/examples/androidclient/src/io/prediction/samples/androidclient/MainActivity.java
@@ -1,6 +1,10 @@
package io.prediction.samples.androidclient;
import io.prediction.Client;
+import io.prediction.ItemRecGetTopNRequestBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
import org.OpenUDID.OpenUDID_manager;
import org.apache.commons.lang3.StringUtils;
@@ -43,30 +47,47 @@
}
// Get recommendations async task
- private class RecsTask extends AsyncTask<Void, Void, String> {
- protected String doInBackground(Void... v) {
+ private class RecsTask extends AsyncTask<Void, Void, Map<String, String[]>> {
+ protected Map<String, String[]> doInBackground(Void... v) {
EditText appKey = (EditText) findViewById(R.id.app_key);
EditText apiUrl = (EditText) findViewById(R.id.api_url);
EditText engine = (EditText) findViewById(R.id.engine);
EditText uid = (EditText) findViewById(R.id.uid);
EditText n = (EditText) findViewById(R.id.n);
+ EditText attributes = (EditText) findViewById(R.id.attributes);
client.setAppkey(appKey.getText().toString());
client.setApiUrl(apiUrl.getText().toString());
- String result = "";
+ Map<String, String[]> results = new HashMap<String, String[]>();
try {
- String[] iids = client.getItemRecTopN(engine.getText()
- .toString(), uid.getText().toString(), Integer
- .parseInt(n.getText().toString()));
- result = StringUtils.join(iids, ",");
+ // Use a builder to insert optional parameter
+ ItemRecGetTopNRequestBuilder builder = client
+ .getItemRecGetTopNRequestBuilder(engine.getText()
+ .toString(), uid.getText().toString(), Integer
+ .parseInt(n.getText().toString()));
+
+ if (!isEmpty(attributes)) {
+ // Include custom attributes in results
+ String[] attributesToGet = attributes.getText().toString()
+ .split(",");
+ builder.attributes(attributesToGet);
+ results = client.getItemRecTopNWithAttributes(builder);
+ } else {
+ results.put("iids", client.getItemRecTopN(builder));
+ }
} catch (Exception e) {
- result = ExceptionUtils.getStackTrace(e);
+ String[] error = { ExceptionUtils.getStackTrace(e) };
+ results.put("error", error);
}
- return result;
+ return results;
}
- protected void onPostExecute(String result) {
+ protected void onPostExecute(Map<String, String[]> result) {
TextView console = (TextView) findViewById(R.id.console_output);
- console.setText(result);
+ String display = "";
+ for (Map.Entry<String, String[]> entry : result.entrySet()) {
+ display += entry.getKey() + ": " + StringUtils.join(entry.getValue(), ",") + "\n";
+ }
+ console.setText(display);
}
}
@@ -84,7 +105,7 @@
String suid = uid.getText().toString();
String siid = iid.getText().toString();
client.userViewItem(suid, siid);
- result = "Logged UID "+suid+" view IID "+siid;
+ result = "Logged UID " + suid + " view IID " + siid;
} catch (Exception e) {
result = ExceptionUtils.getStackTrace(e);
}
@@ -112,7 +133,7 @@
protected void onPostExecute(String result) {
TextView console = (TextView) findViewById(R.id.openudid);
- console.setText("OpenUDID: "+result);
+ console.setText("OpenUDID: " + result);
}
}
@@ -170,4 +191,11 @@
new SaveViewTask().execute();
}
+ private boolean isEmpty(EditText etText) {
+ if (etText.getText().toString().trim().length() > 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
}