edu.byu.hbll.box.internal.web.DocumentService Maven / Gradle / Ivy
/** */
package edu.byu.hbll.box.internal.web;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.core.util.BufferRecyclers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.byu.hbll.box.BoxDocument;
import edu.byu.hbll.box.QueryResult;
import edu.byu.hbll.box.Source;
import edu.byu.hbll.box.internal.core.Registry;
import edu.byu.hbll.box.internal.util.CursorUtils;
import edu.byu.hbll.box.internal.util.FlexDateParser;
/**
* NOTE: The {@link Path} endpoints in this class are designed with an extra unnecessary template in
* order to push them down in matching precedence so as to not hijack all of the application's other
* paths. https://www.safaribooksonline.com/library/view/restful-java-with/9781449361433/ch04.html
*/
@Path("{sourceName}{x: /documents}")
public class DocumentService {
/** */
static final Logger logger = LoggerFactory.getLogger(DocumentService.class);
/** */
private static final ObjectMapper mapper = new ObjectMapper();
/** */
@Inject private Registry registry;
/**
* Post a document to box.
*
* @param json the json document in the body
* @return Response {@link Response}
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
public Response postDocument(String json) {
String sourceName = registry.getPrincipalSource().getName();
String idField = registry.getPrincipalSource().getIdField();
ObjectNode node = null;
try {
node = (ObjectNode) mapper.readTree(json);
} catch (Exception e) {
logger.error("Could not parse posted document as json", e);
return Response.status(Status.BAD_REQUEST)
.entity("The posted document was not valid json")
.build();
}
String id = node.path(idField).asText();
BoxDocument boxDocument = new BoxDocument(id, node);
registry.getDocumentHandler().save(sourceName, boxDocument);
return Response.ok(new WebDocument(boxDocument)).build();
}
/**
* @param params
* @return
*/
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getDocument(@BeanParam DocumentParams params) {
logger.debug("{}", params.getUriInfo().getRequestUri());
try {
registry.verifySource(params.getSourceName());
} catch (Exception e) {
return Response.status(Status.BAD_REQUEST)
.entity(errorMessage("source not recognized"))
.build();
}
List docs;
try {
docs =
registry
.getDocumentHandler()
.find(params.getSourceName(), new DocumentsParams(params).toQuery());
} catch (Exception e) {
logger.error(e.toString(), e);
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(errorMessage("unable to retrieve document: " + e))
.build();
}
BoxDocument doc = docs.isEmpty() ? null : docs.get(0);
WebDocument webDoc = new WebDocument(doc, params.getMetadataLevel());
if (doc.getMessage() != null) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(webDoc).build();
} else if (doc.isDeleted()) {
return Response.status(Status.GONE).entity(webDoc).build();
} else if (!doc.isProcessed()) {
return Response.status(Status.NOT_FOUND).entity(webDoc).build();
}
return Response.ok(webDoc).build();
}
/**
* @param params
* @return
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getDocuments(@BeanParam DocumentsParams params) {
logger.debug("{}", params.getUriInfo().getRequestUri());
String sourceName = params.getSourceName();
try {
sourceName = registry.verifySource(sourceName);
} catch (Exception e) {
return Response.status(Status.BAD_REQUEST)
.entity(errorMessage("source not recognized"))
.build();
}
Source source = registry.getSource(sourceName);
if (params.getLimit() < 0) {
params.setLimit(source.getDefaultLimit());
}
String finalSourceName = sourceName;
if (params.getFromText() != null) {
try {
Instant from = FlexDateParser.parse(params.getFromText());
long cursor = CursorUtils.getCursor(from);
params.setCursor(cursor);
} catch (DateTimeParseException e) {
return Response.status(Status.BAD_REQUEST)
.entity(errorMessage("from parameter must be a ISO 8601 date: " + e))
.build();
}
}
StreamingOutput entity =
new StreamingOutput() {
@Override
public void write(OutputStream out) {
try {
BufferedOutputStream bout = new BufferedOutputStream(out);
PrintWriter writer =
new PrintWriter(new OutputStreamWriter(bout, StandardCharsets.UTF_8));
writer.print("{\"documents\":[");
writer.flush();
QueryResult result =
registry.getDocumentHandler().find(finalSourceName, params.toQuery(), bout);
writer.print("],");
String nextUri =
UriBuilder.fromUri(params.getUriInfo().getRequestUri())
.replaceQueryParam("cursor", result.getNextCursor())
.replaceQueryParam("from")
.build()
.toString();
JsonStringEncoder e = BufferRecyclers.getJsonStringEncoder();
writer.print("\"nextUri\":\"" + new String(e.quoteAsString(nextUri)) + "\",");
writer.print("\"nextCursor\":\"" + result.getNextCursor() + "\"}");
writer.close();
} catch (Exception e) {
logger.error(e.toString(), e);
throw new InternalServerErrorException(
Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(errorMessage("unable to retrieve documents: " + e))
.build());
}
}
};
return Response.ok(entity).build();
}
/**
* @param value
* @return
*/
static boolean booleanValueOf(String value) {
if (value == null) {
return false;
}
switch (value.toLowerCase()) {
case "true":
case "yes":
case "y":
case "":
case "1":
return true;
default:
return false;
}
}
private String errorMessage(String message) {
return mapper.createObjectNode().put("error", message).toString();
}
}