package ca.ualberta.cs.cmput301f14t14.questionapp.data; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.UUID; 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.impl.client.DefaultHttpClient; import android.content.Context; import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import ca.ualberta.cs.cmput301f14t14.questionapp.model.Answer; import ca.ualberta.cs.cmput301f14t14.questionapp.model.Comment; import ca.ualberta.cs.cmput301f14t14.questionapp.model.Image; import ca.ualberta.cs.cmput301f14t14.questionapp.model.Question; import ca.ualberta.cs.cmput301f14t14.questionapp.model.serializer.AnswerDeserializer; import ca.ualberta.cs.cmput301f14t14.questionapp.model.serializer.CommentDeserializer; import ca.ualberta.cs.cmput301f14t14.questionapp.model.serializer.ImageDeserializer; import ca.ualberta.cs.cmput301f14t14.questionapp.model.serializer.ImageSerializer; import ca.ualberta.cs.cmput301f14t14.questionapp.model.serializer.QuestionDeserializer; /** * A data store that uses an ElasticSearch server as a data storage mechanism. */ public class RemoteDataStore implements IDataStore { protected static String ES_BASE_URL = "http://cmput301.softwareprocess.es:8080/cmput301f14t14/"; private static final String QUESTION_PATH = "question/"; private static final String ANSWER_PATH = "answer/"; private static final String QUESTION_COMMENT_PATH = "qcomment/"; private static final String ANSWER_COMMENT_PATH = "acomment/"; private static final String TAG = "RemoteDataStore"; private Context context; private Gson gson; public RemoteDataStore(Context context) { this.context = context; GsonBuilder gb = new GsonBuilder(); // Register serializers and deserializers // Note: The comment stuff is ugly, but it should work gb.registerTypeAdapter(Question.class, new QuestionDeserializer(context)); gb.registerTypeAdapter(Answer.class, new AnswerDeserializer(context)); gb.registerTypeAdapter(new TypeToken<Comment<Question>>() {}.getType(), new CommentDeserializer<Question>()); gb.registerTypeAdapter(new TypeToken<Comment<Answer>>() {}.getType(), new CommentDeserializer<Answer>()); gb.registerTypeAdapter(Image.class, new ImageSerializer()); gb.registerTypeAdapter(Image.class, new ImageDeserializer()); gson = gb.create(); } /** * {@inheritDoc} * * This implementation fetches questions from an ElasticSearch * server using its search interface. */ @Override public List<Question> getQuestionList() throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(ES_BASE_URL + QUESTION_PATH + "_search"); HttpResponse response; try { response = httpClient.execute(httpGet); return getResultList(response, new TypeToken<SearchResponse<Question>>() {}.getType()); } catch (IOException e) { throw new IOException("Error getting question list.", e); } catch (Exception e) { e.printStackTrace(); } return null; } /** * {@inheritDoc} * * This implementation gets all answer children of a question from * an ElasticSearch server. */ public List<Answer> getAnswerList(Question question) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(ES_BASE_URL + ANSWER_PATH + "_search"); httpPost.setEntity(new StringEntity( "{\"query\": {\"has_parent\": {\"type\": \"question\", \"query\": {" + "\"match\": {\"id\": \"" + question.getId() + "\"}}}}}")); HttpResponse response; try { response = httpClient.execute(httpPost); return getResultList(response, new TypeToken<SearchResponse<Answer>>() {}.getType()); } catch (IOException e) { throw new IOException("Error getting answer list.", e); } catch (Exception e) { e.printStackTrace(); } return null; } /** * {@inheritDoc} * * This implementation gets all comment children of a question from * an ElasticSearch server. */ public List<Comment<Question>> getCommentList(Question question) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(ES_BASE_URL + QUESTION_COMMENT_PATH + "_search"); httpPost.setEntity(new StringEntity( "{\"query\": {\"has_parent\": {\"type\": \"question\", \"query\": {" + "\"match\": {\"id\": \"" + question.getId() + "\"}}}}}")); HttpResponse response; try { response = httpClient.execute(httpPost); return getResultList(response, new TypeToken<SearchResponse<Comment<Question>>>() {}.getType()); } catch (IOException e) { throw new IOException("Error getting comment list.", e); } catch (Exception e) { e.printStackTrace(); } return null; } /** * {@inheritDoc} * * This implementation gets all comment children of a question from * an ElasticSearch server. */ public List<Comment<Answer>> getCommentList(Answer answer) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(ES_BASE_URL + ANSWER_COMMENT_PATH + "_search"); httpPost.setEntity(new StringEntity( "{\"query\": {\"has_parent\": {\"type\": \"answer\", \"query\": {" + "\"match\": {\"id\": \"" + answer.getId() + "\"}}}}}")); HttpResponse response; try { response = httpClient.execute(httpPost); return getResultList(response, new TypeToken<SearchResponse<Comment<Answer>>>() {}.getType()); } catch (IOException e) { throw new IOException("Error getting comment list.", e); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void putQuestion(Question question) throws IOException { HttpClient httpClient = new DefaultHttpClient(); try { HttpPost addRequest = new HttpPost(ES_BASE_URL + QUESTION_PATH + question.getId() + "?refresh=true"); StringEntity stringEntity = new StringEntity(gson.toJson(question)); addRequest.setEntity(stringEntity); addRequest.setHeader("Accept", "application/json"); HttpResponse response = httpClient.execute(addRequest); String status = response.getStatusLine().toString(); Log.i(TAG, status); } catch (IOException e) { throw new IOException("Failed to upload question", e); } catch (Exception e) { e.printStackTrace(); } } @Override public Question getQuestion(UUID id) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(ES_BASE_URL + QUESTION_PATH + id.toString()); HttpResponse response; try { response = httpClient.execute(httpGet); SearchHit<Question> sr = parseESResponse(response, new TypeToken<SearchHit<Question>>() {}.getType()); return sr.getSource(); } catch (Exception e) { throw new IOException("Failed to get question", e); } } @Override public void putAnswer(Answer answer) throws IOException { HttpClient httpClient = new DefaultHttpClient(); try { HttpPost addRequest = new HttpPost(ES_BASE_URL + ANSWER_PATH + answer.getId() + "?refresh=true&parent=" + answer.getParent()); StringEntity stringEntity = new StringEntity(gson.toJson(answer)); addRequest.setEntity(stringEntity); addRequest.setHeader("Accept", "application/json"); HttpResponse response = httpClient.execute(addRequest); String status = response.getStatusLine().toString(); Log.i(TAG, status); Log.i(TAG, getEntityContent(response)); } catch (IOException e) { throw new IOException("Failed to upload answer", e); } catch (Exception e) { e.printStackTrace(); } } @Override public Answer getAnswer(UUID id) { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(ES_BASE_URL + ANSWER_PATH + "_search?q=id:" + id.toString()); HttpResponse response; try { response = httpClient.execute(httpGet); SearchResponse<Answer> sr = parseESResponse(response, new TypeToken<SearchResponse<Answer>>() {}.getType()); SearchHit<Answer> hit = sr.getHits().getHits().get(0); return hit.getSource(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void putQComment(Comment<Question> comment) throws IOException { HttpClient httpClient = new DefaultHttpClient(); try { HttpPost addRequest = new HttpPost(ES_BASE_URL + QUESTION_COMMENT_PATH + comment.getId() + "?refresh=true&parent=" + comment.getParent()); StringEntity stringEntity = new StringEntity(gson.toJson(comment, new TypeToken<Comment<Question>>(){}.getType())); addRequest.setEntity(stringEntity); addRequest.setHeader("Accept", "application/json"); HttpResponse response = httpClient.execute(addRequest); String status = response.getStatusLine().toString(); Log.i(TAG, status); } catch (IOException e) { throw new IOException("Failed to save question comment", e); } catch (Exception e) { e.printStackTrace(); } } @Override public void putAComment(Comment<Answer> comment) { DataManager dm = DataManager.getInstance(context); HttpClient httpClient = new DefaultHttpClient(); try { HttpPost addRequest = new HttpPost(ES_BASE_URL + ANSWER_COMMENT_PATH + comment.getId() + "?refresh=true&parent=" + comment.getParent() + "&routing=" + dm.getAnswer(comment.getParent(), null).getParent()); StringEntity stringEntity = new StringEntity(gson.toJson(comment, new TypeToken<Comment<Answer>>(){}.getType())); addRequest.setEntity(stringEntity); addRequest.setHeader("Accept", "application/json"); HttpResponse response = httpClient.execute(addRequest); String status = response.getStatusLine().toString(); Log.i(TAG, status); } catch (Exception e) { e.printStackTrace(); } } @Override public Comment<Question> getQComment(UUID id) { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(ES_BASE_URL + QUESTION_COMMENT_PATH + "_search?q=id:" + id.toString()); HttpResponse response; try { response = httpClient.execute(httpGet); SearchResponse<Comment<Question>> sr = parseESResponse(response, new TypeToken<SearchResponse<Comment<Question>>>() {}.getType()); SearchHit<Comment<Question>> hit = sr.getHits().getHits().get(0); return hit.getSource(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public Comment<Answer> getAComment(UUID id) { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(ES_BASE_URL + ANSWER_COMMENT_PATH + "_search?q=id:" + id.toString()); HttpResponse response; try { response = httpClient.execute(httpGet); SearchResponse<Comment<Answer>> sr = parseESResponse(response, new TypeToken<SearchResponse<Comment<Answer>>>() {}.getType()); SearchHit<Comment<Answer>> hit = sr.getHits().getHits().get(0); return hit.getSource(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void save() { // Do nothing. Always saved. } /** * Parse response from ElasticSearch, which is contained in a SearchHit * object * * @param response * @param type Type of deserialized object * @return SearchHit object from ElasticSearch */ private <T> T parseESResponse(HttpResponse response, Type type) { try { String json = getEntityContent(response); T sr = gson.fromJson(json, type); return sr; } catch (IOException e) { e.printStackTrace(); } return null; } /** * Extract a result list from an http response * @param response * @param type Expected type of deserialized http response body * @return */ private <T> List<T> getResultList(HttpResponse response, Type type) { SearchResponse<T> sr = parseESResponse(response, type); List<SearchHit<T>> hits = sr.getHits().getHits(); List<T> result = new ArrayList<T>(); for (SearchHit<T> hit: hits) { result.add(hit.getSource()); } return result; } /** * Gets content from an HTTP response */ private String getEntityContent(HttpResponse response) throws IOException { BufferedReader rd = new BufferedReader(new InputStreamReader(response .getEntity().getContent())); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } return result.toString(); } }