blob: 3d27919599913c7ab023d882493e0986ac50da93 [file] [log] [blame]
package de.ids_mannheim.korap.response;
import java.util.*;
import java.io.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import de.ids_mannheim.korap.KrillCollection;
import de.ids_mannheim.korap.KrillMeta;
import de.ids_mannheim.korap.KrillQuery;
import de.ids_mannheim.korap.KrillStats;
import de.ids_mannheim.korap.response.Notifications;
import static de.ids_mannheim.korap.util.KrillString.quote;
/**
* Base class for objects meant to be responded by the server.
* This inherits KoralQuery requests.
*
* <p>
* <blockquote><pre>
* Response km = new Response();
* System.out.println(
* km.toJsonString()
* );
* </pre></blockquote>
*
* @author diewald
* @see Notifications
*/
/*
* TODO: Use configuration file to get default token field "tokens"
* TODO: All these fields should be in meta!
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response extends Notifications {
ObjectMapper mapper = new ObjectMapper();
private KrillMeta meta;
private KrillCollection collection;
private KrillQuery query;
private KrillStats stats;
private String version, name, node, listener;
private long totalResources = -2, // Not set
totalResults = -2; // Not set
private String benchmark;
private boolean timeExceeded = false;
private HashMap<String, ObjectNode> jsonFields;
private static final String KORAL_VERSION = "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld";
/**
* Construct a new Response object.
*/
public Response () {};
/**
* Get string representation of the backend's version.
*
* @return String representation of the backend's version
*/
@JsonIgnore
public String getVersion () {
return this.version;
};
/**
* Set the string representation of the backend's version.
*
* @param version
* The string representation of the backend's version
* @return Response object for chaining
*/
public Response setVersion (String fullVersion) {
int found = fullVersion.lastIndexOf('-');
// Is combined name and version
if (found > 0 && (found + 1 < fullVersion.length())) {
this.setName(fullVersion.substring(0, found));
this.version = fullVersion.substring(found + 1);
}
// Is only version number
else {
this.version = fullVersion;
};
return this;
};
/**
* Get string representation of the backend's name.
* All nodes in a cluster should have the same backend name.
*
* @return String representation of the backend's name
*/
@JsonIgnore
public String getName () {
return this.name;
};
/**
* Set the string representation of the backend's name.
* All nodes in a cluster should have the same backend name.
*
* @param name
* The string representation of the backend's name
* @return Response object for chaining
*/
public Response setName (String name) {
this.name = name;
return this;
};
/**
* Get string representation of the node's name.
* Each node in a cluster has a unique name.
*
* @return String representation of the node's name
*/
@JsonIgnore
public String getNode () {
return this.node;
};
/**
* Set the string representation of the node's name.
* Each node in a cluster has a unique name.
*
* @param version
* The string representation of the node's name
* @return Response object for chaining
*/
public Response setNode (String name) {
this.node = name;
return this;
};
/**
* Check if the response time was exceeded.
*
* @return <tt>true</tt> in case the response had a timeout,
* otherwise <tt>false</tt>
*/
@JsonIgnore
public boolean hasTimeExceeded () {
return this.timeExceeded;
};
/**
* Set to <tt>true</tt> if time is exceeded
* based on a timeout.
*
* <p>
* Will add a warning (682) to the output.
*
* @param timeout
* Either <tt>true</tt> or <tt>false</tt>,
* in case the response timed out
* @return Response object for chaining
*/
public Response setTimeExceeded (boolean timeout) {
if (timeout)
this.addWarning(682, "Response time exceeded");
this.timeExceeded = timeout;
return this;
};
/**
* Get the benchmark time as a string.
*
* @return String representation of the benchmark
* (including trailing time unit)
*/
@JsonIgnore
public String getBenchmark () {
return this.benchmark;
};
/**
* Set the benchmark as timestamp differences.
*
* @param ts1
* Starting time of the benchmark
* @param ts2
* Ending time of the benchmark
* @return Response object for chaining
*/
@JsonIgnore
public Response setBenchmark (long ts1, long ts2) {
this.benchmark = (ts2 - ts1) < 100_000_000 ?
// Store as miliseconds
(((double) (ts2 - ts1) * 1e-6) + " ms") :
// Store as seconds
(((double) (ts2 - ts1) / 1000000000.0) + " s");
return this;
};
/**
* Set the benchmark as a string representation.
*
* @param bm
* String representation of a benchmark
* (including trailing time unit)
* @return Response for chaining
*/
public Response setBenchmark (String bm) {
this.benchmark = bm;
return this;
};
/**
* Get the listener URI as a string.
*
* @return The listener URI as a string representation
*/
@JsonIgnore
public String getListener () {
return this.listener;
};
/**
* Set the listener URI as a String. This is probably the
* localhost
* with an arbitrary port, like
*
* <p>
* <blockquote><pre>
* http://localhost:8080/
* </pre></blockquote>
*
* @param listener
* String representation of the listener URI
* @return Response object for chaining
*/
public Response setListener (String listener) {
this.listener = listener;
return this;
};
/**
* Get the total number of results.
*
* @return The total number of results.
*/
@JsonIgnore
public long getTotalResults () {
if (this.totalResults == -2)
return (long) 0;
return this.totalResults;
};
/**
* Set the total number of results.
*
* @param results
* The total number of results.
* @return {link Response} object for chaining.
*/
public Response setTotalResults (long results) {
this.totalResults = results;
return this;
};
/**
* Increment the total number of results by a certain value.
*
* @param incr
* The number of results the total number should
* be incremented by.
* @return {@link Response} object for chaining.
*/
public Response incrTotalResults (int incr) {
if (this.totalResults < 0)
this.totalResults = incr;
else
this.totalResults += incr;
return this;
};
/**
* Get the total number of resources the total number of
* results occur in.
*
* @return The total number of resources the total number of
* results occur in.
*/
@JsonIgnore
public long getTotalResources () {
if (this.totalResources == -2)
return (long) 0;
return this.totalResources;
};
/**
* Set the total number of resources the total number of
* results occur in.
*
* @param resources
* The total number of resources the total
* number of results occur in.
* @return {@link Response} object for chaining.
*/
public Response setTotalResources (long resources) {
this.totalResources = resources;
return this;
};
/**
* Increment the total number of resources the total number
* of results occur in by a certain value.
*
* @param incr
* The number of resources the total number of
* results occur in should be incremented by.
* (I don't care that this isn't English!)
* @return {@link Response} object for chaining.
*/
public Response incrTotalResources (int i) {
if (this.totalResources < 0)
this.totalResources = i;
else
this.totalResources += i;
return this;
};
/**
* Get the KoralQuery query object.
*
* @return The {@link KrillQuery} object,
* representing the KoralQuery query object.
*/
// TODO: "tokens" shouldn't be fixed.
@JsonIgnore
public KrillQuery getQuery () {
if (this.query == null)
this.query = new KrillQuery("tokens");
return this.query;
};
/**
* Set the KoralQuery query object.
*
* @param query
* The {@link KrillQuery} object,
* representing the KoralQuery query object.
* @return The {@link Response} object for chaining
*/
@JsonIgnore
public Response setQuery (KrillQuery query) {
this.query = query;
// Move messages from the query
return (Response) this.moveNotificationsFrom(query);
};
/**
* Get the associated collection object.
* In case no collection information was defined yet,
* a new {@link KrillCollection} object will be created.
*
* @return The attached {@link KrillCollection} object.
*/
@JsonIgnore
public KrillCollection getCollection () {
if (this.collection == null)
this.collection = new KrillCollection();
return this.collection;
};
/**
* Set a new {@link KrillCollection} object.
*
* @param collection
* A {@link KrillCollection} object.
* @return The {@link Response} object for chaining
*/
@JsonIgnore
public Response setCollection (KrillCollection collection) {
this.collection = collection;
// Move messages from the collection
Response resp = (Response) this.moveNotificationsFrom(collection);
return resp;
};
/**
* Get the associated meta object.
* In case no meta information was defined yet,
* a new {@link KrillMeta} object will be created.
*
* @return The attached {@link KrillMeta} object.
*/
@JsonIgnore
public KrillMeta getMeta () {
if (this.meta == null)
this.meta = new KrillMeta();
return this.meta;
};
/**
* Set a new {@link KrillMeta} object.
*
* @param meta
* A {@link KrillMeta} object.
* @return The {@link Response} object for chaining
*/
@JsonIgnore
public Response setMeta (KrillMeta meta) {
this.meta = meta;
// Move messages from the collection
return (Response) this.moveNotificationsFrom(meta);
};
/**
* Get the associated statistics object.
* In case no statistics information was defined yet,
* a new {@link KrillStats} object will be created.
*
* @return The attached {@link KrillStats} object.
*/
@JsonIgnore
public KrillStats getStats () {
if (this.stats == null)
this.stats = new KrillStats();
return this.stats;
};
/**
* Set a new {@link KrillStats} object.
*
* @param stats
* A {@link KrillStats} object.
* @return The {@link Response} object for chaining
*/
@JsonIgnore
public Response setStats (KrillStats stats) {
this.stats = stats;
// Move messages from the stats
return (Response) this.moveNotificationsFrom(stats);
};
public void addJsonNode (String key, ObjectNode value) {
if (this.jsonFields == null)
this.jsonFields = new HashMap<String, ObjectNode>(4);
this.jsonFields.put(key, value);
};
/**
* Serialize response as a {@link JsonNode}.
*
* @return {@link JsonNode} representation of the response
*/
@Override
public JsonNode toJsonNode () {
// Get notifications json response
ObjectNode json = (ObjectNode) super.toJsonNode();
json.put("@context", KORAL_VERSION);
StringBuilder sb = new StringBuilder();
if (this.getName() != null) {
sb.append(this.getName());
if (this.getVersion() != null)
sb.append("-");
};
// No name but version is given
if (this.getVersion() != null)
sb.append(this.getVersion());
// KoralQuery meta object
if (this.meta != null) {
JsonNode metaNode = this.meta.toJsonNode();
if (metaNode != null)
json.put("meta", metaNode);
};
ObjectNode meta = json.has("meta") ? (ObjectNode) json.get("meta")
: (ObjectNode) json.putObject("meta");
if (sb.length() > 0)
meta.put("version", sb.toString());
if (this.timeExceeded)
meta.put("timeExceeded", true);
if (this.getNode() != null)
meta.put("node", this.getNode());
if (this.getListener() != null)
meta.put("listener", this.getListener());
if (this.getBenchmark() != null)
meta.put("benchmark", this.getBenchmark());
// totalResources is set
if (this.totalResources != -2)
meta.put("totalResources", this.totalResources);
// totalResults is set
if (this.totalResults != -2)
meta.put("totalResults", this.totalResults);
// Add json fields as passed to the object
if (this.jsonFields != null) {
json.putAll(this.jsonFields);
};
// KoralQuery query object
if (this.query != null) {
JsonNode queryNode = this.getQuery().toJsonNode();
if (queryNode != null)
json.put("query", queryNode);
};
// KoralQuery collection object
if (this.collection != null) {
// && this.collection.getFilters().toArray().length > 0) {
JsonNode collNode = this.collection.toJsonNode();
if (collNode != null)
json.put("collection", collNode);
};
return (JsonNode) json;
};
/**
* Serialize response as a JSON string.
* <p>
* <blockquote><pre>
* {
* "version" : "Lucene-Backend-0.49.1",
* "timeExceeded" : true,
* "node" : "Tanja",
* "listener" : "http://localhost:8080/",
* "benchmark" : "12.3s",
* "errors": [
* [123, "You are not allowed to serialize these messages"],
* [124, "Your request was invalid"]
* ],
* "messages" : [
* [125, "Class is deprecated", "Notifications"]
* ]
* }
* </pre></blockquote>
*
* @return String representation of the response
*/
public String toJsonString () {
String msg = "";
try {
return mapper.writeValueAsString(this.toJsonNode());
}
catch (Exception e) {
// Bad in case the message contains quotes!
msg = ", " + quote(e.getLocalizedMessage());
};
return "{\"errors\":[" + "[620, " + "\"Unable to generate JSON\"" + msg
+ "]" + "]}";
};
};