/*
* Copyright IBM Corp. 2014
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 net.bluemix.todo.store;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import net.bluemix.todo.model.CloudantAllDocs;
import net.bluemix.todo.model.CloudantCount;
import net.bluemix.todo.model.CloudantPostResponse;
import net.bluemix.todo.model.CloudantRow;
import net.bluemix.todo.model.CloudantToDo;
import net.bluemix.todo.model.ToDo;
/**
* A {@link ToDoStore} using Cloudant.
*/
public class CloudantStore implements ToDoStore {
private static final String REVISION_PARAM = "rev";
private static final String DESIGN_DOC_NAME = "todos";
private static final String VIEW_NAME = "allTodos";
//This JSON represents the design document containing a view we will use when retrieving Todos
private static final String DESIGN_DOC =
"{" +
"\"views\": {" +
"\"allTodos\": {" +
"\"reduce\": \"_count\"," +
"\"map\": \"function(doc){if(doc.title && doc.completed != null){emit(doc.order,{title: doc.title,completed: doc.completed})}}\"" +
"}" +
"}" +
"}";
private static final Logger LOG = Logger.getLogger(CloudantStore.class.getName());
private WebTarget target;
/**
* Creates a CloudantStore.
* @param target The target (URL) for the CloudantStore.
* @throws ToDoStoreException
*/
public CloudantStore(WebTarget target) throws ToDoStoreException {
//Uncomment to enable HTTP logging in Jersey
//target = target.register(new LoggingFilter(LOG, true));
createDB(target);
this.target = target.path("bluemix-todo");
createDesignDoc(this.target);
}
/**
* Will create a database named "todos" if one does not exist.
* @param target The base URL for Cloudant.
* @throws ToDoStoreException Thrown if there is an error creating the DB.
*/
private void createDB(WebTarget target) throws ToDoStoreException {
Response resp = target.path("bluemix-todo").request(MediaType.APPLICATION_JSON).get();;
int status = resp.getStatus();
if(status == HttpURLConnection.HTTP_NOT_FOUND) {
resp = target.path("bluemix-todo").request(MediaType.APPLICATION_JSON).put(Entity.text(""));
status = resp.getStatus();
if(status != HttpURLConnection.HTTP_CREATED && status != HttpURLConnection.HTTP_ACCEPTED) {
throw new ToDoStoreException("Error creating the ToDo database.");
}
}
}
/**
* Will create a design document called "todos" if one does not exist.
* @param target The base URL for Cloudant.
* @throws ToDoStoreException Thrown if there is an error creating the design document.
*/
private static void createDesignDoc(WebTarget target) throws ToDoStoreException {
WebTarget design = target.path("_design").path("todos");
Response resp = design.request(MediaType.APPLICATION_JSON).get();
int status = resp.getStatus();
if(status == HttpURLConnection.HTTP_NOT_FOUND) {
resp = design.request(MediaType.APPLICATION_JSON).put(Entity.json(DESIGN_DOC));
status = resp.getStatus();
if(status != HttpURLConnection.HTTP_CREATED) {
throw new ToDoStoreException("Error creating the ToDo design document.");
}
}
}
@Override
public Collection<ToDo> getAll() throws ToDoStoreException {
Response allDocsResp = target.queryParam("reduce", false).
path("_design").path(DESIGN_DOC_NAME).path("_view").path(VIEW_NAME).
request(MediaType.APPLICATION_JSON).get();
int status = allDocsResp.getStatus();
if(status == HttpURLConnection.HTTP_OK) {
List<ToDo> todos = new ArrayList<ToDo>();
CloudantAllDocs all = allDocsResp.readEntity(CloudantAllDocs.class);
for(CloudantRow row : all.getRows()) {
ToDo td = row.getValue();
td.setId(row.getId());
td.setOrder(row.getKey());
todos.add(td);
}
return todos;
} else {
throw new ToDoStoreException("There was an error retrieving the tasks from Cloudant. Error "
+ status);
}
}
@Override
public ToDo get(String id) throws ToDoStoreException {
Response docResp = getRequest(id);
int status = docResp.getStatus();
if(status == HttpURLConnection.HTTP_OK) {
CloudantToDo td = docResp.readEntity(CloudantToDo.class);
return td.getToDo();
} else {
throw new ToDoStoreException("There was an error retrieving the doc with id " + id
+ "from Cloudant. Error " + status);
}
}
private Response getRequest(String id) {
return target.path(id).request(MediaType.APPLICATION_JSON).get();
}
@Override
public ToDo persist(ToDo td) throws ToDoStoreException {
Response newdoc = target.request(MediaType.APPLICATION_JSON).post(Entity.entity(td, MediaType.APPLICATION_JSON));
int status = newdoc.getStatus();
if(status == HttpURLConnection.HTTP_CREATED) {
CloudantPostResponse post = newdoc.readEntity(CloudantPostResponse.class);
td.setId(post.getId());
return td;
} else {
throw new ToDoStoreException("There was an error POSTing the ToDo to Cloudant. Error "
+ status);
}
}
@Override
public ToDo update(String id, ToDo td) throws ToDoStoreException {
Response docResp = getRequest(id);
int status = docResp.getStatus();
if(status == HttpURLConnection.HTTP_OK) {
CloudantToDo ctd = docResp.readEntity(CloudantToDo.class);
CloudantToDo updatedCtd = new CloudantToDo(td);
updatedCtd.set_rev(ctd.get_rev());
Response updateReq = target.queryParam(REVISION_PARAM, ctd.get_rev()).path(id).
request(MediaType.APPLICATION_JSON).put(Entity.entity(updatedCtd, MediaType.APPLICATION_JSON));
status = updateReq.getStatus();
if(status == HttpURLConnection.HTTP_CREATED) {
CloudantPostResponse post = updateReq.readEntity(CloudantPostResponse.class);
td.setId(post.getId());
return td;
} else {
throw new ToDoStoreException("There was an error POSTing the ToDo to Cloudant. Error "
+ status);
}
} else {
throw new ToDoStoreException("there was an error fetching the ToDo from Cloudant. Error " + status);
}
}
@Override
public void delete(String id) throws ToDoStoreException {
Response docResp = getRequest(id);
int status = docResp.getStatus();
if(status == HttpURLConnection.HTTP_OK) {
CloudantToDo ctd = docResp.readEntity(CloudantToDo.class);
Response updateReq = target.queryParam(REVISION_PARAM, ctd.get_rev()).path(id).
request(MediaType.APPLICATION_JSON).delete();
status = updateReq.getStatus();
if(status != HttpURLConnection.HTTP_OK) {
throw new ToDoStoreException("There was an error deleting the ToDo from Cloudant. Error "
+ status);
}
} else {
throw new ToDoStoreException("There was an error getting the ToDo from Cloudant. Error " + status);
}
}
@Override
public int count() throws ToDoStoreException {
Response allDocsResp = target.path("_design").path(DESIGN_DOC_NAME).path("_view").path(VIEW_NAME).
request(MediaType.APPLICATION_JSON).get();
int status = allDocsResp.getStatus();
if(status == HttpURLConnection.HTTP_OK) {
CloudantCount count = allDocsResp.readEntity(CloudantCount.class);
if(count.getRows().size() > 0) {
return count.getRows().get(0).getValue();
} else {
//No rows means there are no documents in the view.
return 0;
}
} else {
throw new ToDoStoreException("There was an error retrieving the tasks from Cloudant. Error "
+ status);
}
}
}