
eu.fbk.knowledgestore.server.http.jaxrs.Root Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ks-server-http Show documentation
Show all versions of ks-server-http Show documentation
The HTTP server module (ks-server-http) implements the Web API of
the KnowledgeStore, which includes the two CRUD and SPARQL endpoints.
The CRUD Endpoint supports the retrieval and manipulation of
semi-structured data about resource, mention, entity and axiom records
(encoded in RDF, possibly using JSONLD), and the upload / download of
resource representation. The SPARQL Endpoint supports SPARQL SELECT,
CONSTRUCT, DESCRIBE and ASK queries according to the W3C SPARQL
protocol. The two endpoints are implemented on top of a component
implementing the KnowledgeStore Java API (the Store interface), which
can be either the the KnowledgeStore frontend (ks-frontend) or the Java
Client. The implementation of the module is based on the Jetty Web sever
(run in embedded mode) and the Jersey JAX-RS implementation. Reference
documentation of the Web API is automatically generated using the
Enunciate tool.
package eu.fbk.knowledgestore.server.http.jaxrs;
import java.io.InputStream;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import org.codehaus.enunciate.Facet;
import org.glassfish.jersey.server.mvc.Viewable;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.BooleanLiteralImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.query.BindingSet;
import org.openrdf.query.impl.ListBindingSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.fbk.knowledgestore.OperationException;
import eu.fbk.knowledgestore.Outcome;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.data.Representation;
import eu.fbk.knowledgestore.data.Stream;
import eu.fbk.knowledgestore.internal.Util;
import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
import eu.fbk.knowledgestore.server.http.UIConfig.Example;
import eu.fbk.knowledgestore.vocabulary.KS;
import eu.fbk.knowledgestore.vocabulary.NIE;
import eu.fbk.knowledgestore.vocabulary.NIF;
@Path("/")
@Facet(name = "internal")
public class Root extends Resource {
private static final Logger LOGGER = LoggerFactory.getLogger(Root.class);
private static final String VERSION = Util.getVersion("eu.fbk.knowledgestore", "ks-core",
"devel");
private static final URI NUM_MENTIONS = new URIImpl(KS.NAMESPACE + "numMentions");
private static final List DESCRIBE_VARS = ImmutableList.of("subject", "predicate",
"object", "graph");
private static final int MAX_FETCHED_RESULTS = 10000;
// private static final Pattern NIF_OFFSET_PATTERN = Pattern.compile("char=(\\d+),(\\d+)");
@GET
public Response getStatus() {
String uri = getUriInfo().getRequestUri().toString();
uri = (uri.endsWith("/") ? uri : uri + "/") + "ui";
final Response redirect = Response.status(Status.FOUND).location(java.net.URI.create(uri))
.build();
throw new WebApplicationException(redirect);
}
@GET
@Path("/static/{name:.*}")
public Response download(@PathParam("name") final String name) throws Throwable {
final InputStream stream = Root.class.getResourceAsStream(name);
if (stream == null) {
throw new WebApplicationException("No resource named " + name, Status.NOT_FOUND);
}
final String type = Data.extensionToMimeType(name);
init(false, type, null, null);
final CacheControl control = new CacheControl();
control.setMaxAge(3600 * 24);
control.setMustRevalidate(true);
control.setPrivate(false);
return newResponseBuilder(Status.OK, stream, null).cacheControl(control).build();
}
@GET
@Path("/ui")
@Produces("text/html;charset=UTF-8")
public Viewable ui() throws Throwable {
final Map model = Maps.newHashMap();
model.put("maxTriples", getUIConfig().getResultLimit());
String view = "/status";
final String action = getParameter("action", String.class, null, model);
final Long timeoutSec = getParameter("timeout", Long.class, null, model);
final Long timeout = timeoutSec == null ? null : timeoutSec * 1000;
final int limit = getParameter("limit", Integer.class, getUIConfig().getResultLimit(),
model);
try {
if ("lookup".equals(action)) {
final URI id = getParameter("id", URI.class, null, model);
final URI selection = getParameter("selection", URI.class, null, model);
view = "/lookup";
model.put("tabLookup", Boolean.TRUE);
uiLookup(model, id, selection, limit);
} else if ("sparql".equals(action)) {
final String query = getParameter("query", String.class, null, model);
view = "/sparql";
model.put("tabSparql", Boolean.TRUE);
uiSparql(model, query, timeout);
} else if ("entity-mentions".equals(action)) {
final URI entityID = getParameter("entity", URI.class, null, model);
final URI property = getParameter("property", URI.class, null, model);
final Value value = getParameter("value", Value.class, null, model);
view = "/entity-mentions";
model.put("tabReports", Boolean.TRUE);
model.put("subtabEntityMentions", Boolean.TRUE);
uiReportEntityMentions(model, entityID, property, value, limit);
} else if ("entity-mentions-aggregate".equals(action)) {
final URI entityID = getParameter("entity", URI.class, null, model);
view = "/entity-mentions-aggregate";
model.put("tabReports", Boolean.TRUE);
model.put("subtabEntityMentionsAggregate", Boolean.TRUE);
uiReportEntityMentionsAggregate(model, entityID);
} else if ("mention-value-occurrences".equals(action)) {
final URI entityID = getParameter("entity", URI.class, null, model);
final URI property = getParameter("property", URI.class, null, model);
view = "/mention-value-occurrences";
model.put("tabReports", Boolean.TRUE);
model.put("subtabMentionValueOccurrences", Boolean.TRUE);
uiReportMentionValueOccurrences(model, entityID, property);
} else if ("mention-property-occurrences".equals(action)) {
final URI entityID = getParameter("entity", URI.class, null, model);
view = "/mention-property-occurrences";
model.put("tabReports", Boolean.TRUE);
model.put("subtabMentionPropertyOccurrences", Boolean.TRUE);
uiReportMentionPropertyOccurrences(model, entityID);
} else {
uiStatus(model);
}
} catch (final Throwable ex) {
if (ex instanceof OperationException) {
final OperationException oex = (OperationException) ex;
model.put("error", oex.getOutcome().toString());
if (oex.getOutcome().getStatus() == Outcome.Status.ERROR_UNEXPECTED) {
LOGGER.error("Unexpected error", ex);
}
} else {
model.put("error", ex.getMessage());
LOGGER.error("Unexpected error", ex);
}
}
return new Viewable(view, model);
}
@SuppressWarnings("unchecked")
private T getParameter(final String name, final Class clazz,
@Nullable final T defaultValue, @Nullable final Map model) {
T result = defaultValue;
final String stringValue = getUriInfo().getQueryParameters().getFirst(name);
if (stringValue != null && !"".equals(stringValue)) {
if (Value.class.isAssignableFrom(clazz)) {
final char c = stringValue.charAt(0);
if (c == '\'' || c == '"' || c == '<' || //
stringValue.indexOf(':') >= 0 && stringValue.indexOf('/') < 0) {
try {
final Value value = Data.parseValue(stringValue, Data.getNamespaceMap());
if (clazz.isInstance(value)) {
result = clazz.cast(value);
}
} catch (final Throwable ex) {
// ignore
}
}
if (result == defaultValue) {
if (URI.class.equals(clazz)) {
result = (T) Data.getValueFactory().createURI(Data.cleanIRI(stringValue));
} else if (clazz.isAssignableFrom(Literal.class)) {
result = (T) Data.getValueFactory().createLiteral(stringValue);
}
}
} else {
result = Data.convert(stringValue, clazz, defaultValue);
}
}
if (result != null) {
model.put(name, result);
}
return result;
}
private void uiStatus(final Map model) {
// Emit uptime and percentage spent in GC
final StringBuilder builder = new StringBuilder();
final long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
final long days = uptime / (24 * 60 * 60 * 1000);
final long hours = uptime / (60 * 60 * 1000) - days * 24;
final long minutes = uptime / (60 * 1000) - (days * 24 + hours) * 60;
long gctime = 0;
for (final GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
gctime += bean.getCollectionTime(); // assume 1 bean or they don't work in parallel
}
builder.append(days == 0 ? "" : days + "d").append(hours == 0 ? "" : hours + "h")
.append(minutes).append("m uptime, ").append(gctime * 100 / uptime).append("% gc");
// Emit memory usage
final MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
final MemoryUsage nonHeap = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
final long used = heap.getUsed() + nonHeap.getUsed();
final long committed = heap.getCommitted() + nonHeap.getCommitted();
final long mb = 1024 * 1024;
long max = 0;
for (final MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
max += bean.getPeakUsage().getUsed(); // assume maximum at same time in all pools
}
builder.append("; ").append(used / mb).append("/").append(max / mb).append("/")
.append(committed / mb).append(" MB memory used/peak/committed");
// Emit thread numbers
final int numThreads = ManagementFactory.getThreadMXBean().getThreadCount();
final int maxThreads = ManagementFactory.getThreadMXBean().getPeakThreadCount();
final long startedThreads = ManagementFactory.getThreadMXBean()
.getTotalStartedThreadCount();
builder.append("; ").append(numThreads).append("/").append(maxThreads).append("/")
.append(startedThreads).append(" threads active/peak/started");
model.put("version", VERSION);
model.put("status", builder.toString());
}
private void uiSparql(final Map model, @Nullable final String query,
@Nullable final Long timeout) throws Throwable {
// Emit the example queries
if (!getUIConfig().getSparqlExamples().isEmpty()) {
final List links = Lists.newArrayList();
final StringBuilder script = new StringBuilder();
int index = 0;
for (final Example example : getUIConfig().getSparqlExamples()) {
links.add("" + RenderUtils.escapeHtml(example.getLabel()) + "");
script.append("if (queryNum == ").append(index).append(") {\n");
script.append(" return \"")
.append(example.getValue().replace("\n", "\\n").replace("\"", "\\\""))
.append("\";\n");
script.append("}\n");
++index;
}
script.append("return \"\";\n");
model.put("examplesScript", script.toString());
model.put("examplesLinks", links);
}
// Emit the query and evaluate its results, if possible
if (query != null) {
// Emit the query results (only partially materialized).
final long ts = System.currentTimeMillis();
final Stream stream = sendQuery(query, timeout);
@SuppressWarnings("unchecked")
final List vars = stream.getProperty("variables", List.class);
final Iterator iterator = stream.iterator();
final List fetched = ImmutableList.copyOf(Iterators.limit(iterator,
MAX_FETCHED_RESULTS));
model.put(
"results",
RenderUtils.renderSolutionTable(vars,
Iterables.concat(fetched, Stream.create(iterator))));
final long elapsed = System.currentTimeMillis() - ts;
// Emit the results message
final StringBuilder builder = new StringBuilder();
if (fetched.size() < MAX_FETCHED_RESULTS) {
builder.append(fetched.size()).append(" results in ");
builder.append(elapsed).append(" ms");
} else {
builder.append("more than ").append(MAX_FETCHED_RESULTS);
}
if (timeout != null && elapsed > timeout) {
builder.append(" (timed out, more results may be available)");
}
model.put("resultsMessage", builder.toString());
}
}
private void uiLookup(final Map model, @Nullable final URI id,
@Nullable final URI selection, final int limit) throws Throwable {
if (!getUIConfig().getLookupExamples().isEmpty()) {
model.put("examplesCount", getUIConfig().getLookupExamples().size());
model.put("examples", getUIConfig().getLookupExamples());
}
if (id != null) {
if (!uiLookupResource(model, id, selection, limit) //
&& !uiLookupMention(model, id, limit) //
&& !uiLookupEntity(model, id, limit)) {
model.put("text", "NO ENTRY FOR ID " + id);
}
}
}
private boolean uiLookupResource(final Map model, final URI resourceID,
final URI selection, final int limit) throws Throwable {
// Retrieve the resource record for the URI specified. Return false if not found
final Record resource = getRecord(KS.RESOURCE, resourceID);
if (resource == null) {
return false;
}
// Get mentions, generating links and identifying selected mentions and entities
final List mentions = getResourceMentions(resourceID);
URI selectedEntityID = null;
Record selectedMention = null;
final List mentionLinks = Lists.newArrayList();
final Set entityLinks = Sets.newTreeSet();
final String linkTemplate = "%s";
for (final Record mention : mentions) {
final URI mentionID = mention.getID();
mentionLinks.add(String.format(linkTemplate, mentionID,
RenderUtils.shortenURI(mentionID)));
if (mention.getID().equals(selection)) {
selectedMention = mention;
}
for (final URI entityID : mention.get(KS.REFERS_TO, URI.class)) {
entityLinks.add(String.format(linkTemplate, entityID,
RenderUtils.shortenURI(entityID)));
if (entityID.equals(selection)) {
selectedEntityID = selection;
}
}
}
// Select the resource template
model.put("resource", Boolean.TRUE);
// Emit mentions and entities dropdown lists
if (!mentionLinks.isEmpty()) {
model.put("resourceMentionsCount", mentionLinks.size());
model.put("resourceMentions", mentionLinks);
}
if (!entityLinks.isEmpty()) {
model.put("resourceEntitiesCount", entityLinks.size());
model.put("resourceEntities", entityLinks);
}
// Emit resource text box
final Representation representation = getRepresentation(resourceID);
if (representation != null) {
final String text = representation.writeToString();
final StringBuilder builder = new StringBuilder();
if (!mentions.isEmpty()) {
RenderUtils.renderText(text, mentions, selection, true, false, getUIConfig(),
builder);
} else {
final Record metadata = representation.getMetadata();
model.put("resourcePrettyPrint", Boolean.TRUE);
RenderUtils.renderText(text, metadata.getUnique(NIE.MIME_TYPE, String.class),
builder);
}
model.put("resourceText", builder.toString());
}
// Emit the details box (mention / entity / resource metadata)
if (selectedEntityID != null) {
// One entity selected - emit its describe triples
final List bindings = getEntityDescribeTriples(selection, limit);
final int total = bindings.size() < limit ? bindings.size()
: countEntityDescribeTriples(selection);
model.put("resourceDetailsBody",
String.join("", RenderUtils.renderSolutionTable(DESCRIBE_VARS, bindings)));
model.put("resourceDetailsTitle", String.format(" Entity %s "
+ "(%d triples out of %d)", RenderUtils.render(selection),
bindings.size(), total));
} else if (selectedMention != null) {
// One mention selected - emit its details
final StringBuilder builder = new StringBuilder("Mention ");
RenderUtils.render(selection, builder);
builder.append("");
final List entityURIs = selectedMention.get(KS.REFERS_TO, URI.class);
if (!entityURIs.isEmpty()) {
builder.append(" ➟ ")
.append(entityURIs.size() == 1 ? "Entity" : "Entities")
.append("");
for (final URI entityURI : entityURIs) {
builder.append(" ");
RenderUtils.render(entityURI, builder);
builder.append(" (select)");
}
}
model.put("resourceDetailsTitle", builder.toString());
model.put("resourceDetailsBody", RenderUtils.render(selectedMention));
} else {
// Nothing selected - emit resource metadata
model.put("resourceDetailsTitle", "Resource metadata");
model.put("resourceDetailsBody", RenderUtils.render(resource));
}
// Signal success
return true;
}
private boolean uiLookupMention(final Map model, final URI mentionID,
final int limit) throws Throwable {
// Retrieve the mention for the URI specified. Return false if not found
final Record mention = getRecord(KS.MENTION, mentionID);
if (mention == null) {
return false;
}
// Select the mention template
model.put("mention", Boolean.TRUE);
// Emit the mention description box
model.put("mentionData", RenderUtils.render(mention));
// Emit the resource box, including the mention snipped
final URI resourceID = mention.getUnique(KS.MENTION_OF, URI.class, null);
if (resourceID != null) {
model.put("mentionResourceLink",
RenderUtils.render(resourceID, mentionID, new StringBuilder()).toString());
final Representation representation = getRepresentation(resourceID);
if (representation == null) {
model.put("mentionResourceExcerpt", "RESOURCE CONTENT NOT AVAILABLE");
} else {
final String text = representation.writeToString();
model.put("mentionResourceExcerpt", RenderUtils.renderText(text,
ImmutableList.of(mention), null, false, true, getUIConfig(),
new StringBuilder()));
}
}
// Emit the denoted entities box
final List entityIDs = mention.get(KS.REFERS_TO, URI.class);
if (!entityIDs.isEmpty()) {
// Emit the triples of the first denoted entity, including their total number
final URI entityID = entityIDs.iterator().next();
final List describeTriples = getEntityDescribeTriples(entityID, limit);
final int total = describeTriples.size() < limit ? describeTriples.size()
: countEntityDescribeTriples(entityID);
model.put("mentionEntityTriplesShown", describeTriples.size());
model.put("mentionEntityTriplesTotal", total);
model.put("mentionEntityTriples", String.join("", RenderUtils.renderSolutionTable( //
ImmutableList.of("subject", "predicate", "object", "graph"), describeTriples)));
// Emit the link(s) to the pages for all the denoted entities
if (entityIDs.size() == 1) {
model.put("mentionEntityLink", RenderUtils.render(entityID));
} else {
final StringBuilder builder = new StringBuilder();
for (final URI id : entityIDs) {
builder.append(builder.length() > 0 ? " " : "");
RenderUtils.render(id, builder);
}
model.put("mentionEntityLinks", builder.toString());
}
}
// Signal success
return true;
}
private boolean uiLookupEntity(final Map model, final URI entityID,
final int limit) throws Throwable {
// Lookup (a subset of) describe triples and graph triples for the specified entity
final List describeTriples = getEntityDescribeTriples(entityID, limit);
final List graphTriples = getEntityGraphTriples(entityID, limit);
if (describeTriples.isEmpty() && graphTriples.isEmpty()) {
return false;
}
// Select the entity template
model.put("entity", Boolean.TRUE);
// Emit the describe box
if (!describeTriples.isEmpty()) {
final int total = describeTriples.size() < limit ? describeTriples.size()
: countEntityDescribeTriples(entityID);
model.put("entityTriplesShown", describeTriples.size());
model.put("entityTriplesTotal", total);
model.put("entityTriples", String.join("", RenderUtils.renderSolutionTable( //
ImmutableList.of("subject", "predicate", "object", "graph"), describeTriples)));
}
// Emit the graph box
if (!graphTriples.isEmpty()) {
final int total = graphTriples.size() < limit ? graphTriples.size()
: countEntityGraphTriples(entityID);
model.put("entityGraphShown", graphTriples.size());
model.put("entityGraphTotal", total);
model.put("entityGraph", String.join("", RenderUtils.renderSolutionTable( //
ImmutableList.of("subject", "predicate", "object"), graphTriples)));
}
// Emit the resources box
final List resources = getEntityResources(entityID, getUIConfig().getResultLimit());
if (!resources.isEmpty()) {
// Emit resource and mention counts
final int[] counts = countEntityResourcesAndMentions(entityID);
model.put("entityResourcesShown", resources.size());
model.put("entityResourcesCount", counts[0]);
model.put("entityMentionsCount", counts[1]);
// Emit the resources table
final StringBuilder builder = new StringBuilder();
final List overviewProperties = getUIConfig().getResourceOverviewProperties();
final int width = 75 / (overviewProperties.size() + 2);
final String th = "";
builder.append("\n");
builder.append("").append(th).append("resource ID");
for (final URI property : overviewProperties) {
builder.append(th)
.append(RenderUtils.escapeHtml(Data.toString(property,
Data.getNamespaceMap()))).append("");
}
builder.append(th);
if (resources.size() < getUIConfig().getResultLimit()) {
builder.append("# mentions");
} else {
builder.append("# mentions (truncated)");
}
builder.append(" \n\n");
for (final Record resource : resources) {
builder.append("");
RenderUtils.render(resource.getID(), entityID, builder);
for (final URI property : overviewProperties) {
builder.append(" ");
RenderUtils.render(resource.get(property), builder);
}
builder.append(" ");
RenderUtils.render(resource.getUnique(NUM_MENTIONS, Integer.class, null), builder);
builder.append(" \n");
}
builder.append("
");
model.put("entityResources", builder.toString());
}
// Signal success
return true;
}
private void uiReportEntityMentions(final Map model,
@Nullable final URI entityID, @Nullable final URI property,
@Nullable final Value value, final int limit) throws Throwable {
// Do nothing in case the entity ID is missing
if (entityID == null) {
return;
}
// Retrieve all the mentions satisfying the property[=value] optional filter
int numMentions = 0;
final List mentions = Lists.newArrayList();
for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
if (property == null || !mention.isNull(property)
&& (value == null || mention.get(property).contains(value))) {
++numMentions;
if (mentions.size() < limit) {
mentions.add(mention);
}
}
}
// Render the mention table, including column toggling functionality
model.put("message", mentions.size() + " mentions shown out of " + numMentions);
model.put("mentionTable",
RenderUtils.renderRecordsTable(new StringBuilder(), mentions, null, null));
}
private void uiReportEntityMentionsAggregate(final Map model,
final URI entityID) throws Throwable {
// Do nothing in case the entity ID is missing
if (entityID == null) {
return;
}
// Render the table
final Stream mentions = getEntityMentions(entityID, Integer.MAX_VALUE, null);
final Predicate filter = Predicates.not(Predicates.in(ImmutableSet.of(
NIF.BEGIN_INDEX, NIF.END_INDEX, KS.MENTION_OF)));
final String linkTemplate = "ui?action=entity-mentions&entity="
+ UrlEscapers.urlFormParameterEscaper().escape(entityID.stringValue())
+ "&property=${property}&value=${value}";
model.put("propertyValuesTable", RenderUtils.renderRecordsAggregateTable(
new StringBuilder(), mentions, filter, linkTemplate, null));
}
private void uiReportMentionValueOccurrences(final Map model,
final URI entityID, @Nullable final URI property) throws Throwable {
// Do nothing in case the entity ID is missing
if (entityID == null || property == null) {
return;
}
// Compute the # of occurrences of all the values of the given property in entity mentions
final Multiset propertyValues = HashMultiset.create();
for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
propertyValues.addAll(mention.get(property, Value.class));
}
// Render the table
final Escaper esc = UrlEscapers.urlFormParameterEscaper();
final String linkTemplate = "ui?action=entity-mentions&entity="
+ esc.escape(entityID.stringValue()) + "&property="
+ esc.escape(Data.toString(property, Data.getNamespaceMap()))
+ "&value=${element}";
model.put("valueOccurrencesTable", RenderUtils.renderMultisetTable(new StringBuilder(),
propertyValues, "Property value", "# Mentions", linkTemplate));
}
private void uiReportMentionPropertyOccurrences(final Map model,
final URI entityID) throws Throwable {
// Do nothing in case the entity ID is missing
if (entityID == null) {
return;
}
// Compute the # of occurrences of each property URI in entity mentions
final Multiset propertyURIs = HashMultiset.create();
for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
propertyURIs.addAll(mention.getProperties());
}
// Render the table
final Escaper esc = UrlEscapers.urlFormParameterEscaper();
final String linkTemplate = "ui?action=entity-mentions&entity="
+ esc.escape(entityID.stringValue()) + "&property=${element}";
model.put("propertyOccurrencesTable", RenderUtils.renderMultisetTable(new StringBuilder(),
propertyURIs, "Property", "# Mentions", linkTemplate));
}
// DATA ACCESS METHODS
@Nullable
private Record getRecord(final URI layer, @Nullable final URI id) throws Throwable {
final Record record = id == null ? null : getSession().retrieve(layer).ids(id).exec()
.getUnique();
if (record != null && layer.equals(KS.MENTION)) {
final String template = "SELECT ?e WHERE { ?e $$ $$ "
+ (getUIConfig().isDenotedByAllowsGraphs() ? ""
: "FILTER NOT EXISTS { GRAPH ?e { ?s ?p ?o } } ") + "}";
for (final URI entityID : getSession()
.sparql(template, getUIConfig().getDenotedByProperty(), id).execTuples()
.transform(URI.class, true, "e")) {
record.add(KS.REFERS_TO, entityID);
}
}
return record;
}
@Nullable
private Representation getRepresentation(@Nullable final URI resourceID) throws Throwable {
final Representation representation = resourceID == null ? null : getSession().download(
resourceID).exec();
if (representation != null) {
closeOnCompletion(representation);
}
return representation;
}
private List getResourceMentions(final URI resourceID) throws Throwable {
final Record resource;
resource = getSession().retrieve(KS.RESOURCE).ids(resourceID).exec().getUnique();
if (resource == null) {
return Collections.emptyList();
}
final Map mentions = Maps.newHashMap();
final List mentionIDs = resource.get(KS.HAS_MENTION, URI.class);
if (mentionIDs.isEmpty()) {
return Collections.emptyList();
}
for (final Record mention : getSession().retrieve(KS.MENTION).ids(mentionIDs).exec()) {
mentions.put(mention.getID(), mention);
}
final Set entityIDs = Sets.newHashSet();
for (final Record mention : mentions.values()) {
for (final URI entityID : mention.get(KS.REFERS_TO, URI.class)) {
entityIDs.add(entityID);
}
}
for (final List ids : Stream.create(mentionIDs).chunk(128)) {
final StringBuilder builder = new StringBuilder();
builder.append("SELECT ?m ?e WHERE { ?e ");
builder.append(Data.toString(getUIConfig().getDenotedByProperty(), null));
builder.append(" ?m VALUES ?m {");
for (final URI mentionID : ids) {
builder.append(' ').append(Data.toString(mentionID, null));
}
builder.append(" } ");
if (!getUIConfig().isDenotedByAllowsGraphs()) {
builder.append("FILTER NOT EXISTS { GRAPH ?e { ?s ?p ?o } } ");
}
builder.append("}");
for (final BindingSet bindings : getSession().sparql(builder.toString()).execTuples()) {
final URI mentionID = (URI) bindings.getValue("m");
final URI entityID = (URI) bindings.getValue("e");
mentions.get(mentionID).add(KS.REFERS_TO, entityID);
entityIDs.add(entityID);
}
}
// FOLLOWING CODE CAN INCREASE THE NUMBER OF MENTIONS RETRIEVED, BUT THE QUERY USED MAY
// TAKE UP TO SOME HUNDRED OF SECONDS (AND USING A TIMEOUT PRODUCES A NON-DETERMINISTIC
// OUTPUT
// if (!entityIDs.isEmpty()) {
// builder = new StringBuilder();
// builder.append("SELECT ?m ?e WHERE { "
// + "VALUES ?p { sem:hasActor sem:hasTime sem:hasPlace } VALUES ?e0 {");
// for (final URI entityID : entityIDs) {
// builder.append(' ').append(Data.toString(entityID, null));
// }
// builder.append(" } ?e0 ?p ?e . ?e $$ ?m FILTER(STRSTARTS(STR(?m), $$)) }");
// for (final BindingSet bindings : getSession()
// .sparql(builder.toString(), getUIConfig().getDenotedByProperty(),
// resourceID.stringValue()).timeout(1000L).execTuples()) {
// final URI mentionID = (URI) bindings.getValue("m");
// final URI entityID = (URI) bindings.getValue("e");
// Record mention = mentions.get(mentionID);
// if (mention == null) {
// mention = Record.create(mentionID, KS.MENTION);
// final Matcher matcher = NIF_OFFSET_PATTERN.matcher(mentionID.stringValue());
// if (matcher.find()) {
// mention.set(NIF.BEGIN_INDEX, Integer.parseInt(matcher.group(1)));
// mention.set(NIF.END_INDEX, Integer.parseInt(matcher.group(2)));
// }
// mentions.put(mentionID, mention);
// }
// mention.add(KS.REFERS_TO, entityID);
// }
// }
final List sortedMentions = Lists.newArrayList(mentions.values());
Collections.sort(sortedMentions, new Comparator() {
@Override
public int compare(final Record r1, final Record r2) {
final int begin1 = r1.getUnique(NIF.BEGIN_INDEX, Integer.class, 0);
final int begin2 = r2.getUnique(NIF.BEGIN_INDEX, Integer.class, 0);
int result = Integer.compare(begin1, begin2);
if (result == 0) {
final int end1 = r1.getUnique(NIF.END_INDEX, Integer.class, Integer.MAX_VALUE);
final int end2 = r2.getUnique(NIF.END_INDEX, Integer.class, Integer.MAX_VALUE);
result = Integer.compare(end1, end2); // longest mention last
}
return result;
}
});
return sortedMentions;
}
private Stream getEntityMentions(final URI entityID, final int maxResults,
@Nullable final int[] numMentions) throws Throwable {
// First retrieve all the URIs of the mentions denoting the entity, via SPARQL query
final List mentionURIs = getSession()
.sparql("SELECT ?m WHERE { $$ $$ ?m}", entityID,
getUIConfig().getDenotedByProperty()).execTuples()
.transform(URI.class, true, "m").toList();
// Return the total number of mentions, if an holder variable has been supplied
if (numMentions != null) {
numMentions[0] = mentionURIs.size();
}
// Then return a stream that returns the mention records as they are fetched from the KS
return getSession().retrieve(KS.MENTION).limit((long) maxResults).ids(mentionURIs).exec();
}
private List getEntityResources(final URI entityID, final int maxResults)
throws Throwable {
// Retrieve up to maxResults IDs of resources mentioning the entity
final Multiset resourceIDs = HashMultiset.create();
try (Stream stream = getSession()
.sparql("SELECT ?m WHERE { $$ $$ ?m }", entityID,
getUIConfig().getDenotedByProperty()).execTuples()
.transform(URI.class, true, "m")) {
for (final URI mentionID : stream) {
final String string = mentionID.stringValue();
final int index = string.indexOf("#");
if (index > 0) {
final URI resourceID = Data.getValueFactory().createURI(
string.substring(0, index));
if (resourceIDs.elementSet().size() == maxResults
&& !resourceIDs.contains(resourceID)) {
break;
}
resourceIDs.add(resourceID);
}
}
}
// Lookup the resources in the KS
final List resources;
resources = getSession().retrieve(KS.RESOURCE).ids(resourceIDs).exec().toList();
for (final Record resource : resources) {
resource.set(NUM_MENTIONS, resourceIDs.count(resource.getID()));
}
return resources;
}
private List getEntityDescribeTriples(final URI entityID, final int limit)
throws Throwable {
return getSession()
.sparql("SELECT (COALESCE(?s, $$) AS ?subject) ?predicate "
+ "(COALESCE(?o, $$) AS ?object) ?graph "
+ "WHERE { { GRAPH ?graph { $$ ?predicate ?o } } UNION "
+ "{ GRAPH ?graph { ?s ?predicate $$ } } } LIMIT $$", entityID, entityID,
entityID, entityID, limit).execTuples().toList();
}
private List getEntityGraphTriples(final URI entityID, final int limit)
throws Throwable {
return getSession()
.sparql("SELECT ?subject ?predicate ?object "
+ "WHERE { GRAPH $$ { ?subject ?predicate ?object } } LIMIT $$", entityID,
limit).execTuples().toList();
}
private int countEntityDescribeTriples(final URI entityID) throws Throwable {
return getSession()
.sparql("SELECT (COUNT(*) AS ?n) "
+ "WHERE { { GRAPH ?g { $$ ?p ?o } } UNION { GRAPH ?g { ?s ?p $$ } } }",
entityID, entityID).execTuples().transform(Integer.class, true, "n")
.getUnique();
}
private int countEntityGraphTriples(final URI entityID) throws Throwable {
return getSession()
.sparql("SELECT (COUNT(*) AS ?n) WHERE { GRAPH $$ { ?s ?p ?o } }", entityID)
.execTuples().transform(Integer.class, true, "n").getUnique();
}
private int[] countEntityResourcesAndMentions(final URI entityID) throws Throwable {
final BindingSet tuple = getSession()
.sparql("SELECT (COUNT(DISTINCT ?r) AS ?nr) (COUNT(*) AS ?nm) "
+ "WHERE { $$ $$ ?m . BIND(IRI(STRBEFORE(STR(?m), \"#\")) AS ?r) }",
entityID, getUIConfig().getDenotedByProperty()).execTuples().getUnique();
return new int[] { ((Literal) tuple.getValue("nr")).intValue(),
((Literal) tuple.getValue("nm")).intValue() };
}
private Stream sendQuery(final String query, final Long timeout) throws Throwable {
final String form = RDFUtil.detectSparqlForm(query);
if (form.equalsIgnoreCase("select")) {
return closeOnCompletion(getSession().sparql(query).timeout(timeout).execTuples());
} else if (form.equalsIgnoreCase("construct") || form.equals("describe")) {
final List variables = ImmutableList.of("subject", "predicate", "object");
final Function transformer = new Function() {
@Override
public BindingSet apply(final Statement statement) {
return new ListBindingSet(variables, statement.getSubject(),
statement.getPredicate(), statement.getObject());
}
};
final Stream stream = getSession().sparql(query).timeout(timeout)
.execTriples().transform(transformer, 1);
stream.setProperty("variables", variables);
return closeOnCompletion(stream);
} else {
final boolean result = getSession().sparql(query).timeout(timeout).execBoolean();
final List variables = ImmutableList.of("result");
final BindingSet bindings = new ListBindingSet(variables,
BooleanLiteralImpl.valueOf(result));
return closeOnCompletion(Stream.create(new BindingSet[] { bindings }).setProperty(
"variables", variables));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy