/******************************************************************************
*
* Copyright 2014 Paphus Solutions Inc.
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package com.paphus.sdk;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.paphus.sdk.config.BrowseConfig;
import com.paphus.sdk.config.ChannelConfig;
import com.paphus.sdk.config.ChatConfig;
import com.paphus.sdk.config.ChatResponse;
import com.paphus.sdk.config.ContentConfig;
import com.paphus.sdk.config.DomainConfig;
import com.paphus.sdk.config.ForumConfig;
import com.paphus.sdk.config.ForumPostConfig;
import com.paphus.sdk.config.InstanceConfig;
import com.paphus.sdk.config.MediaConfig;
import com.paphus.sdk.config.TrainingConfig;
import com.paphus.sdk.config.UserAdminConfig;
import com.paphus.sdk.config.UserConfig;
import com.paphus.sdk.config.VoiceConfig;
import com.paphus.sdk.config.WebMediumConfig;
/**
* Connection class for a REST service connection.
* The SDK connection gives you access to the paphus or libre server services using a REST API.
* <p>
* The services include:
* <ul>
* <li> User management (account creation, validation)
* <li> Bot access, chat, and administration
* <li> Forum access, posting, and administration
* <li> Live chat access, chat, and administration
* <li> Domain access, and administration
* </ul>
*/
public class SDKConnection {
protected static String[] types = new String[]{"Bots", "Forums", "Live Chat", "Domains"};
protected static String[] channelTypes = new String[]{"ChatRoom", "OneOnOne"};
protected static String[] accessModes = new String[]{"Everyone", "Users", "Members", "Administrators"};
protected static String[] learningModes = new String[]{"Disabled", "Administrators", "Users", "Everyone"};
protected static String[] correctionModes = new String[]{"Disabled", "Administrators", "Users", "Everyone"};
protected static String[] botModes = new String[]{"ListenOnly", "AnswerOnly", "AnswerAndListen"};
protected String applicationId;
protected String url;
protected UserConfig user;
protected DomainConfig domain;
protected Credentials credentials;
protected boolean debug = false;
protected SDKException exception;
/**
* Return the name of the default user image.
*/
public static String defaultUserImage() {
return "images/user-thumb.jpg";
}
/**
* Create an SDK connection with the credentials.
* Use the Credentials subclass specific to your server.
*/
public SDKConnection(Credentials credentials) {
this.credentials = credentials;
this.url = credentials.url;
}
/**
* Validate the user credentials (password, or token).
* The user details are returned (with a connection token, password removed).
* The user credentials are soted in the connection, and used on subsequent calls.
* An SDKException is thrown if the connect failed.
*/
public UserConfig connect(UserConfig config) {
this.user = fetch(config);
return this.user;
}
/**
* Connect to the live chat channel and return a LiveChatConnection.
* A LiveChatConnection is separate from an SDKConnection and uses web sockets for
* asynchronous communication.
* The listener will be notified of all messages.
*/
public LiveChatConnection openLiveChat(ChannelConfig channel, LiveChatListener listener) {
LiveChatConnection connection = new LiveChatConnection(this.credentials, listener);
connection.connect(channel, this.user);
return connection;
}
/**
* Connect to the domain.
* A domain is an isolated content space.
* Any browse or query request will be specific to the domain's content.
*/
public DomainConfig connect(DomainConfig config) {
this.domain = fetch(config);
return this.domain;
}
/**
* Disconnect from the connection.
* An SDKConnection does not keep a live connection, but this resets its connected user and domain.
*/
public void disconnect() {
this.user = null;
this.domain = null;
}
/**
* Fetch the user details for the user credentials.
* A token or password is required to validate the user.
*/
public UserConfig fetch(UserConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/check-user", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
UserConfig user = new UserConfig();
user.parseXML(root);
return user;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Fetch the URL for the image from the server.
*/
public URL fetchImage(String image) {
try {
return new URL("http://" + this.credentials.host + this.credentials.app + "/" + image);
} catch (Exception exception) {
this.exception = new SDKException(exception);
throw this.exception;
}
}
/**
* Fetch the forum post details for the forum post id.
*/
public ForumPostConfig fetch(ForumPostConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/check-forum-post", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
ForumPostConfig post = new ForumPostConfig();
post.parseXML(root);
return post;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Create a new user.
*/
public UserConfig create(UserConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/create-user", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
UserConfig user = new UserConfig();
user.parseXML(root);
this.user = user;
return user;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Create a new forum post.
* You must set the forum id for the post.
*/
public ForumPostConfig create(ForumPostConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/create-forum-post", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
ForumPostConfig post = new ForumPostConfig();
post.parseXML(root);
return post;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Create a new file/image/media attachment for a chat channel.
*/
public MediaConfig createChannelFileAttachment(String file, MediaConfig config) {
config.addCredentials(this);
String xml = POSTFILE(this.url + "/create-channel-attachment", file, config.name, config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
MediaConfig media = new MediaConfig();
media.parseXML(root);
return media;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Create a new file/image/media attachment for a chat channel.
*/
public MediaConfig createChannelImageAttachment(String file, MediaConfig config) {
config.addCredentials(this);
String xml = POSTIMAGE(this.url + "/create-channel-attachment", file, config.name, config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
MediaConfig media = new MediaConfig();
media.parseXML(root);
return media;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Create a reply to a forum post.
* You must set the parent id for the post replying to.
*/
public ForumPostConfig createReply(ForumPostConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/create-reply", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
ForumPostConfig reply = new ForumPostConfig();
reply.parseXML(root);
return reply;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Fetch the content details from the server.
* The id or name and domain of the object must be set.
*/
@SuppressWarnings("unchecked")
public <T extends WebMediumConfig> T fetch(T config) {
config.addCredentials(this);
String xml = POST(this.url + "/check-" + config.getType(), config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
config = (T)config.getClass().newInstance();
config.parseXML(root);
return config;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Update the forum post.
*/
public ForumPostConfig update(ForumPostConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/update-forum-post", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
config = new ForumPostConfig();
config.parseXML(root);
return config;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Update the user details.
* The password must be passed to allow the update.
*/
public UserConfig update(UserConfig config) {
config.addCredentials(this);
POST(this.url + "/update-user", config.toXML());
return config;
}
/**
* Permanently delete the forum post with the id.
*/
public void delete(ForumPostConfig config) {
config.addCredentials(this);
POST(this.url + "/delete-forum-post", config.toXML());
}
/**
* Flag the content as offensive, a reason is required.
*/
public void flag(WebMediumConfig config) {
config.addCredentials(this);
POST(this.url + "/flag-" + config.getType(), config.toXML());
}
/**
* Flag the forum post as offensive, a reason is required.
*/
public void flag(ForumPostConfig config) {
config.addCredentials(this);
POST(this.url + "/flag-forum-post", config.toXML());
}
/**
* Flag the user post as offensive, a reason is required.
*/
public void flag(UserConfig config) {
config.addCredentials(this);
POST(this.url + "/flag-user", config.toXML());
}
/**
* Process the bot chat message and return the bot's response.
* The ChatConfig should contain the conversation id if part of a conversation.
* If a new conversation the conversation id is returned in the response.
*/
public ChatResponse chat(ChatConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/post-chat", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
ChatResponse response = new ChatResponse();
response.parseXML(root);
return response;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Return the list of user details for the comma separated values list of user ids.
*/
public List<UserConfig> getUsers(String usersCSV) {
UserConfig config = new UserConfig();
config.user = usersCSV;
config.addCredentials(this);
String xml = POST(this.url + "/get-users", config.toXML());
List<UserConfig> users = new ArrayList<UserConfig>();
Element root = parse(xml);
if (root == null) {
return users;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
Element child = (Element)root.getChildNodes().item(index);
UserConfig userConfig = new UserConfig();
userConfig.parseXML(child);
users.add(userConfig);
}
return users;
}
/**
* Return the list of forum posts for the forum browse criteria.
*/
public List<ForumPostConfig> getPosts(BrowseConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/get-forum-posts", config.toXML());
List<ForumPostConfig> instances = new ArrayList<ForumPostConfig>();
Element root = parse(xml);
if (root == null) {
return instances;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
ForumPostConfig post = new ForumPostConfig();
post.parseXML((Element)root.getChildNodes().item(index));
instances.add(post);
}
return instances;
}
/**
* Return the list of categories for the type, and domain.
*/
public List<String> getCategories(ContentConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/get-categories", config.toXML());
List<String> categories = new ArrayList<String>();
categories.add("");
Element root = parse(xml);
if (root == null) {
return categories;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
categories.add(((Element)root.getChildNodes().item(index)).getAttribute("name"));
}
return categories;
}
/**
* Return the list of tags for the type, and domain.
*/
public List<String> getTags(ContentConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/get-tags", config.toXML());
List<String> tags = new ArrayList<String>();
tags.add("");
Element root = parse(xml);
if (root == null) {
return tags;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
tags.add(((Element)root.getChildNodes().item(index)).getAttribute("name"));
}
return tags;
}
/**
* Return the users for the content.
*/
public List<String> getUsers(WebMediumConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/get-" + config.getType() + "-users", config.toXML());
List<String> users = new ArrayList<String>();
Element root = parse(xml);
if (root == null) {
return users;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
UserConfig user = new UserConfig();
user.parseXML((Element)root.getChildNodes().item(index));
users.add(user.user);
}
return users;
}
/**
* Train the bot with a new question/response pair.
*/
public void train(TrainingConfig config) {
config.addCredentials(this);
POST(this.url + "/train-instance", config.toXML());
}
/**
* Perform the user administration task (add or remove users, or administrators).
*/
public void userAdmin(UserAdminConfig config) {
config.addCredentials(this);
POST(this.url + "/user-admin", config.toXML());
}
/**
* Update the user's icon.
* The file will be uploaded to the server.
*/
public UserConfig updateIcon(String file, UserConfig config) {
config.addCredentials(this);
String xml = POSTIMAGE(this.url + "/update-user-icon", file, "image.jpg", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
config = new UserConfig();
config.parseXML(root);
return config;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
protected String POSTIMAGE(String url, String file, String name, String xml) {
if (this.debug) {
System.out.println("POST: " + url);
System.out.println("file: " + file);
System.out.println("XML: " + xml);
}
String result = "";
try {
Bitmap bitmap = loadImage(file, 300, 300);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
byte[] byte_arr = stream.toByteArray();
ByteArrayBody fileBody = new ByteArrayBody(byte_arr, name);
MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntity.addPart("file", fileBody);
multipartEntity.addPart("xml", new StringBody(xml));
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = null;
HttpPost httppost = new HttpPost(url);
httppost.setEntity(multipartEntity);
response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity, HTTP.UTF_8);
}
if ((response.getStatusLine().getStatusCode() != 200) && (response.getStatusLine().getStatusCode() != 204)) {
this.exception = new SDKException(""
+ response.getStatusLine().getStatusCode()
+ " : " + result);
throw this.exception;
}
} catch (Exception exception) {
this.exception = new SDKException(exception);
throw this.exception;
}
return result;
}
protected String POSTFILE(String url, String path, String name, String xml) {
if (this.debug) {
System.out.println("POST: " + url);
System.out.println("file: " + path);
System.out.println("XML: " + xml);
}
String result = "";
try {
File file = new File(path);
FileInputStream stream = new FileInputStream(file);
ByteArrayOutputStream output = new ByteArrayOutputStream();
int read = 0;
byte[] buffer = new byte[4096];
while ((read = stream.read(buffer)) != -1 ) {
output.write(buffer, 0, read);
}
byte[] byte_arr = output.toByteArray();
stream.close();
ByteArrayBody fileBody = new ByteArrayBody(byte_arr, name);
MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntity.addPart("file", fileBody);
multipartEntity.addPart("xml", new StringBody(xml));
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = null;
HttpPost httppost = new HttpPost(url);
httppost.setEntity(multipartEntity);
response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity, HTTP.UTF_8);
}
if ((response.getStatusLine().getStatusCode() != 200) && (response.getStatusLine().getStatusCode() != 204)) {
this.exception = new SDKException(""
+ response.getStatusLine().getStatusCode()
+ " : " + result);
throw this.exception;
}
} catch (Exception exception) {
this.exception = new SDKException(exception);
throw this.exception;
}
return result;
}
protected Bitmap loadImage(String path, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int height = options.outHeight;
int width = options.outWidth;
options.inPreferredConfig = Bitmap.Config.RGB_565;
int inSampleSize = 1;
if (height > reqHeight) {
inSampleSize = Math.round((float)height / (float)reqHeight);
}
int expectedWidth = width / inSampleSize;
if (expectedWidth > reqWidth) {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
/**
* Return the bot's voice configuration.
*/
public VoiceConfig getVoice(InstanceConfig config) {
config.addCredentials(this);
String xml = POST(this.url + "/get-voice", config.toXML());
Element root = parse(xml);
if (root == null) {
return null;
}
try {
VoiceConfig voice = new VoiceConfig();
voice.parseXML(root);
return voice;
} catch (Exception exception) {
this.exception = SDKException.parseFailure(exception);
throw this.exception;
}
}
/**
* Return the list of content for the browse criteria.
* The type defines the content type (one of Bot, Forum, Channel, Domain).
*/
public List<WebMediumConfig> browse(BrowseConfig config) {
config.addCredentials(this);
String type = "";
if (config.type.equals("Bot")) {
type = "/get-instances";
} else if (config.type.equals("Forum")) {
type = "/get-forums";
} else if (config.type.equals("Channel")) {
type = "/get-channels";
} else if (config.type.equals("Domain")) {
type = "/get-domains";
}
String xml = POST(this.url + type, config.toXML());
List<WebMediumConfig> instances = new ArrayList<WebMediumConfig>();
Element root = parse(xml);
if (root == null) {
return instances;
}
for (int index = 0; index < root.getChildNodes().getLength(); index++) {
WebMediumConfig instance = null;
if (config.type.equals("Bot")) {
instance = new InstanceConfig();
} else if (config.type.equals("Forum")) {
instance = new ForumConfig();
} else if (config.type.equals("Channel")) {
instance = new ChannelConfig();
} else if (config.type.equals("Domain")) {
instance = new DomainConfig();
}
instance.parseXML((Element)root.getChildNodes().item(index));
instances.add(instance);
}
return instances;
}
/**
* Return the list of content types.
*/
public String[] getTypes() {
return types;
}
/**
* Return the current connected user.
*/
public UserConfig getUser() {
return user;
}
/**
* Set the current connected user.
* connect() should be used to validate and connect a user.
*/
public void setUser(UserConfig user) {
this.user = user;
}
/**
* Return the current domain.
* A domain is an isolated content space.
*/
public DomainConfig getDomain() {
return domain;
}
/**
* Set the current domain.
* A domain is an isolated content space.
* connect() should be used to validate and connect a domain.
*/
public void setDomain(DomainConfig domain) {
this.domain = domain;
}
/**
* Return the current application credentials.
*/
public Credentials getCredentials() {
return credentials;
}
/**
* Set the application credentials.
*/
public void setCredentials(Credentials credentials) {
this.credentials = credentials;
}
/**
* Return is debugging has been enabled.
*/
public boolean isDebug() {
return debug;
}
/**
* Enable debugging, debug messages will be logged to System.out.
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Return the last thrown exception.
*/
public SDKException getException() {
return exception;
}
protected void setException(SDKException exception) {
this.exception = exception;
}
protected String GET(String url) {
if (this.debug) {
System.out.println("GET: " + url);
}
String xml = null;
try {
HttpClient httpClient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet, localContext);
HttpEntity entity = response.getEntity();
xml = EntityUtils.toString(entity, HTTP.UTF_8);
if (response.getStatusLine().getStatusCode() != 200) {
this.exception = new SDKException(""
+ response.getStatusLine().getStatusCode()
+ " : " + xml);
return "";
}
} catch (Exception exception) {
if (this.debug) {
exception.printStackTrace();
}
this.exception = new SDKException(exception);
throw this.exception;
}
return xml;
}
protected String POST(String url, String xml) {
if (this.debug) {
System.out.println("POST: " + url);
System.out.println("XML: " + xml);
}
String result = "";
try {
HttpClient httpClient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpPost httpPost = new HttpPost(url);
StringEntity content = new StringEntity(xml, "utf-8");
content.setContentType("application/xml");
httpPost.setEntity(content);
HttpResponse response = httpClient.execute(httpPost, localContext);
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity, HTTP.UTF_8);
}
if ((response.getStatusLine().getStatusCode() != 200) && (response.getStatusLine().getStatusCode() != 204)) {
this.exception = new SDKException(""
+ response.getStatusLine().getStatusCode()
+ " : " + result);
throw this.exception;
}
} catch (Exception exception) {
if (this.debug) {
exception.printStackTrace();
}
this.exception = new SDKException(exception);
throw this.exception;
}
return result;
}
protected Element parse(String xml) {
if (this.debug) {
System.out.println(xml);
}
Document dom = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource source = new InputSource();
source.setCharacterStream(new StringReader(xml));
dom = builder.parse(source);
return dom.getDocumentElement();
} catch (Exception exception) {
if (this.debug) {
exception.printStackTrace();
}
this.exception = new SDKException(exception.getMessage(), exception);
throw this.exception;
}
}
}