1 package eu.fbk.knowledgestore.server.http.jaxrs;
2
3 import com.google.common.base.Function;
4 import com.google.common.base.Predicate;
5 import com.google.common.base.Predicates;
6 import com.google.common.collect.*;
7 import com.google.common.escape.Escaper;
8 import com.google.common.net.UrlEscapers;
9 import eu.fbk.knowledgestore.OperationException;
10 import eu.fbk.knowledgestore.Outcome;
11 import eu.fbk.knowledgestore.data.Data;
12 import eu.fbk.knowledgestore.data.Record;
13 import eu.fbk.knowledgestore.data.Representation;
14 import eu.fbk.knowledgestore.data.Stream;
15 import eu.fbk.knowledgestore.internal.Util;
16 import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
17 import eu.fbk.knowledgestore.server.http.UIConfig.Example;
18 import eu.fbk.knowledgestore.vocabulary.KS;
19 import eu.fbk.knowledgestore.vocabulary.NIE;
20 import eu.fbk.knowledgestore.vocabulary.NIF;
21 import org.codehaus.enunciate.Facet;
22 import org.glassfish.jersey.server.mvc.Viewable;
23 import org.openrdf.model.Literal;
24 import org.openrdf.model.Statement;
25 import org.openrdf.model.URI;
26 import org.openrdf.model.Value;
27 import org.openrdf.model.impl.BooleanLiteralImpl;
28 import org.openrdf.model.impl.URIImpl;
29 import org.openrdf.query.BindingSet;
30 import org.openrdf.query.impl.ListBindingSet;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import javax.annotation.Nullable;
35 import javax.ws.rs.*;
36 import javax.ws.rs.core.CacheControl;
37 import javax.ws.rs.core.Response;
38 import javax.ws.rs.core.Response.Status;
39 import java.io.InputStream;
40 import java.lang.management.GarbageCollectorMXBean;
41 import java.lang.management.ManagementFactory;
42 import java.lang.management.MemoryPoolMXBean;
43 import java.lang.management.MemoryUsage;
44 import java.util.*;
45
46 @Path("/")
47 @Facet(name = "internal")
48 public class Root extends Resource {
49
50 private static final Logger LOGGER = LoggerFactory.getLogger(Root.class);
51
52 private static final String VERSION = Util.getVersion("eu.fbk.knowledgestore", "ks-core",
53 "devel");
54
55 private static final URI NUM_MENTIONS = new URIImpl(KS.NAMESPACE + "numMentions");
56
57 private static final List<String> DESCRIBE_VARS = ImmutableList.of("subject", "predicate",
58 "object", "graph");
59
60 private static final int MAX_FETCHED_RESULTS = 10000;
61
62
63
64 @GET
65 public Response getStatus() {
66 String uri = getUriInfo().getRequestUri().toString();
67 uri = (uri.endsWith("/") ? uri : uri + "/") + "ui";
68 final Response redirect = Response.status(Status.FOUND).location(java.net.URI.create(uri))
69 .build();
70 throw new WebApplicationException(redirect);
71 }
72
73 @GET
74 @Path("/static/{name:.*}")
75 public Response download(@PathParam("name") final String name) throws Throwable {
76 final InputStream stream = Root.class.getResourceAsStream(name);
77 if (stream == null) {
78 throw new WebApplicationException("No resource named " + name, Status.NOT_FOUND);
79 }
80 final String type = Data.extensionToMimeType(name);
81 init(false, type, null, null);
82 final CacheControl control = new CacheControl();
83 control.setMaxAge(3600 * 24);
84 control.setMustRevalidate(true);
85 control.setPrivate(false);
86 return newResponseBuilder(Status.OK, stream, null).cacheControl(control).build();
87 }
88
89 @GET
90 @Path("/ui")
91 @Produces("text/html;charset=UTF-8")
92 public Viewable ui() throws Throwable {
93
94 final Map<String, Object> model = Maps.newHashMap();
95 model.put("maxTriples", getUIConfig().getResultLimit());
96 String view = "/status";
97
98 final String action = getParameter("action", String.class, null, model);
99 final Long timeoutSec = getParameter("timeout", Long.class, null, model);
100 final Long timeout = timeoutSec == null ? null : timeoutSec * 1000;
101 final int limit = getParameter("limit", Integer.class, getUIConfig().getResultLimit(),
102 model);
103
104 try {
105 if ("lookup".equals(action)) {
106 final URI id = getParameter("id", URI.class, null, model);
107 final URI selection = getParameter("selection", URI.class, null, model);
108 view = "/lookup";
109 model.put("tabLookup", Boolean.TRUE);
110 uiLookup(model, id, selection, limit);
111
112 } else if ("sparql".equals(action)) {
113 final String query = getParameter("query", String.class, null, model);
114 view = "/sparql";
115 model.put("tabSparql", Boolean.TRUE);
116 uiSparql(model, query, timeout);
117
118 } else if ("entity-mentions".equals(action)) {
119 final URI entityID = getParameter("entity", URI.class, null, model);
120 final URI property = getParameter("property", URI.class, null, model);
121 final Value value = getParameter("value", Value.class, null, model);
122 view = "/entity-mentions";
123 model.put("tabReports", Boolean.TRUE);
124 model.put("subtabEntityMentions", Boolean.TRUE);
125 uiReportEntityMentions(model, entityID, property, value, limit);
126
127 } else if ("entity-mentions-aggregate".equals(action)) {
128 final URI entityID = getParameter("entity", URI.class, null, model);
129 view = "/entity-mentions-aggregate";
130 model.put("tabReports", Boolean.TRUE);
131 model.put("subtabEntityMentionsAggregate", Boolean.TRUE);
132 uiReportEntityMentionsAggregate(model, entityID);
133
134 } else if ("mention-value-occurrences".equals(action)) {
135 final URI entityID = getParameter("entity", URI.class, null, model);
136 final URI property = getParameter("property", URI.class, null, model);
137 view = "/mention-value-occurrences";
138 model.put("tabReports", Boolean.TRUE);
139 model.put("subtabMentionValueOccurrences", Boolean.TRUE);
140 uiReportMentionValueOccurrences(model, entityID, property);
141
142 } else if ("mention-property-occurrences".equals(action)) {
143 final URI entityID = getParameter("entity", URI.class, null, model);
144 view = "/mention-property-occurrences";
145 model.put("tabReports", Boolean.TRUE);
146 model.put("subtabMentionPropertyOccurrences", Boolean.TRUE);
147 uiReportMentionPropertyOccurrences(model, entityID);
148
149 } else {
150 uiStatus(model);
151 }
152
153 } catch (final Throwable ex) {
154 if (ex instanceof OperationException) {
155 final OperationException oex = (OperationException) ex;
156 model.put("error", oex.getOutcome().toString());
157 if (oex.getOutcome().getStatus() == Outcome.Status.ERROR_UNEXPECTED) {
158 LOGGER.error("Unexpected error", ex);
159 }
160 } else {
161 model.put("error", ex.getMessage());
162 LOGGER.error("Unexpected error", ex);
163 }
164 }
165
166 return new Viewable(view, model);
167 }
168
169 @SuppressWarnings("unchecked")
170 private <T> T getParameter(final String name, final Class<T> clazz,
171 @Nullable final T defaultValue, @Nullable final Map<String, Object> model) {
172 T result = defaultValue;
173 final String stringValue = getUriInfo().getQueryParameters().getFirst(name);
174 if (stringValue != null && !"".equals(stringValue)) {
175 if (Value.class.isAssignableFrom(clazz)) {
176 final char c = stringValue.charAt(0);
177 if (c == '\'' || c == '"' || c == '<' ||
178 stringValue.indexOf(':') >= 0 && stringValue.indexOf('/') < 0) {
179 try {
180 final Value value = Data.parseValue(stringValue, Data.getNamespaceMap());
181 if (clazz.isInstance(value)) {
182 result = clazz.cast(value);
183 }
184 } catch (final Throwable ex) {
185
186 }
187 }
188 if (result == defaultValue) {
189 if (URI.class.equals(clazz)) {
190 result = (T) Data.getValueFactory().createURI(Data.cleanIRI(stringValue));
191 } else if (clazz.isAssignableFrom(Literal.class)) {
192 result = (T) Data.getValueFactory().createLiteral(stringValue);
193 }
194 }
195 } else {
196 result = Data.convert(stringValue, clazz, defaultValue);
197 }
198 }
199 if (result != null) {
200 model.put(name, result);
201 }
202 return result;
203 }
204
205 private void uiStatus(final Map<String, Object> model) {
206
207
208 final StringBuilder builder = new StringBuilder();
209 final long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
210 final long days = uptime / (24 * 60 * 60 * 1000);
211 final long hours = uptime / (60 * 60 * 1000) - days * 24;
212 final long minutes = uptime / (60 * 1000) - (days * 24 + hours) * 60;
213 long gctime = 0;
214 for (final GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
215 gctime += bean.getCollectionTime();
216 }
217 builder.append(days == 0 ? "" : days + "d").append(hours == 0 ? "" : hours + "h")
218 .append(minutes).append("m uptime, ").append(gctime * 100 / uptime).append("% gc");
219
220
221 final MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
222 final MemoryUsage nonHeap = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
223 final long used = heap.getUsed() + nonHeap.getUsed();
224 final long committed = heap.getCommitted() + nonHeap.getCommitted();
225 final long mb = 1024 * 1024;
226 long max = 0;
227 for (final MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
228 max += bean.getPeakUsage().getUsed();
229 }
230 builder.append("; ").append(used / mb).append("/").append(max / mb).append("/")
231 .append(committed / mb).append(" MB memory used/peak/committed");
232
233
234 final int numThreads = ManagementFactory.getThreadMXBean().getThreadCount();
235 final int maxThreads = ManagementFactory.getThreadMXBean().getPeakThreadCount();
236 final long startedThreads = ManagementFactory.getThreadMXBean()
237 .getTotalStartedThreadCount();
238 builder.append("; ").append(numThreads).append("/").append(maxThreads).append("/")
239 .append(startedThreads).append(" threads active/peak/started");
240
241 model.put("version", VERSION);
242 model.put("status", builder.toString());
243 }
244
245 private void uiSparql(final Map<String, Object> model, @Nullable final String query,
246 @Nullable final Long timeout) throws Throwable {
247
248
249 if (!getUIConfig().getSparqlExamples().isEmpty()) {
250 final List<String> links = Lists.newArrayList();
251 final StringBuilder script = new StringBuilder();
252 int index = 0;
253 for (final Example example : getUIConfig().getSparqlExamples()) {
254 links.add("<a href=\"#\" onclick=\"$('#query').val(sparqlExample(" + index
255 + "))\">" + RenderUtils.escapeJavaScriptString(example.getLabel()) + "</a>");
256 script.append("if (queryNum == ").append(index).append(") {\n");
257 script.append(" return \"")
258 .append(example.getValue().replace("\n", "\\n").replace("\"", "\\\""))
259 .append("\";\n");
260 script.append("}\n");
261 ++index;
262 }
263 script.append("return \"\";\n");
264 model.put("examplesScript", script.toString());
265 model.put("examplesLinks", links);
266 }
267
268
269 if (query != null) {
270
271
272 final long ts = System.currentTimeMillis();
273 final Stream<BindingSet> stream = sendQuery(query, timeout);
274 @SuppressWarnings("unchecked")
275 final List<String> vars = stream.getProperty("variables", List.class);
276 final Iterator<BindingSet> iterator = stream.iterator();
277 final List<BindingSet> fetched = ImmutableList.copyOf(Iterators.limit(iterator,
278 MAX_FETCHED_RESULTS));
279 model.put(
280 "results",
281 RenderUtils.renderSolutionTable(vars,
282 Iterables.concat(fetched, Stream.create(iterator))));
283 final long elapsed = System.currentTimeMillis() - ts;
284
285
286 final StringBuilder builder = new StringBuilder();
287 if (fetched.size() < MAX_FETCHED_RESULTS) {
288 builder.append(fetched.size()).append(" results in ");
289 builder.append(elapsed).append(" ms");
290 } else {
291 builder.append("more than ").append(MAX_FETCHED_RESULTS);
292 }
293 if (timeout != null && elapsed > timeout) {
294 builder.append(" (timed out, more results may be available)");
295 }
296 model.put("resultsMessage", builder.toString());
297 }
298 }
299
300 private void uiLookup(final Map<String, Object> model, @Nullable final URI id,
301 @Nullable final URI selection, final int limit) throws Throwable {
302
303 if (!getUIConfig().getLookupExamples().isEmpty()) {
304 model.put("examplesCount", getUIConfig().getLookupExamples().size());
305 model.put("examples", getUIConfig().getLookupExamples());
306 }
307
308 if (id != null) {
309 if (!uiLookupResource(model, id, selection, limit)
310 && !uiLookupMention(model, id, limit)
311 && !uiLookupEntity(model, id, limit)) {
312 model.put("text", "NO ENTRY FOR ID " + id);
313 }
314 }
315 }
316
317 private boolean uiLookupResource(final Map<String, Object> model, final URI resourceID,
318 final URI selection, final int limit) throws Throwable {
319
320
321 final Record resource = getRecord(KS.RESOURCE, resourceID);
322 if (resource == null) {
323 return false;
324 }
325
326
327 final List<Record> mentions = getResourceMentions(resourceID);
328 URI selectedEntityID = null;
329 Record selectedMention = null;
330 final List<String> mentionLinks = Lists.newArrayList();
331 final Set<String> entityLinks = Sets.newTreeSet();
332 final String linkTemplate = "<a onclick=\"select('%s')\" href=\"#\">%s</a>";
333 for (final Record mention : mentions) {
334 final URI mentionID = mention.getID();
335 mentionLinks.add(String.format(linkTemplate, RenderUtils.escapeJavaScriptString(mentionID),
336 RenderUtils.shortenURI(mentionID)));
337 if (mention.getID().equals(selection)) {
338 selectedMention = mention;
339 }
340 for (final URI entityID : mention.get(KS.REFERS_TO, URI.class)) {
341 entityLinks.add(String.format(linkTemplate, RenderUtils.escapeJavaScriptString(entityID),
342 RenderUtils.shortenURI(entityID)));
343 if (entityID.equals(selection)) {
344 selectedEntityID = selection;
345 }
346 }
347 }
348
349
350 model.put("resource", Boolean.TRUE);
351
352
353 if (!mentionLinks.isEmpty()) {
354 model.put("resourceMentionsCount", mentionLinks.size());
355 model.put("resourceMentions", mentionLinks);
356 }
357 if (!entityLinks.isEmpty()) {
358 model.put("resourceEntitiesCount", entityLinks.size());
359 model.put("resourceEntities", entityLinks);
360 }
361
362
363 final Representation representation = getRepresentation(resourceID);
364 if (representation != null) {
365 final String text = representation.writeToString();
366 final StringBuilder builder = new StringBuilder();
367 if (!mentions.isEmpty()) {
368 RenderUtils.renderText(text, mentions, selection, true, false, getUIConfig(),
369 builder);
370 } else {
371 final Record metadata = representation.getMetadata();
372 model.put("resourcePrettyPrint", Boolean.TRUE);
373 RenderUtils.renderText(text, metadata.getUnique(NIE.MIME_TYPE, String.class),
374 builder);
375 }
376 model.put("resourceText", builder.toString());
377 }
378
379
380 if (selectedEntityID != null) {
381
382 final List<BindingSet> bindings = getEntityDescribeTriples(selection, limit);
383 final int total = bindings.size() < limit ? bindings.size()
384 : countEntityDescribeTriples(selection);
385 model.put("resourceDetailsBody",
386 String.join("", RenderUtils.renderSolutionTable(DESCRIBE_VARS, bindings)));
387 model.put("resourceDetailsTitle", String.format("<strong> Entity %s "
388 + "(%d triples out of %d)</strong>", RenderUtils.render(selection),
389 bindings.size(), total));
390
391 } else if (selectedMention != null) {
392
393 final StringBuilder builder = new StringBuilder("<strong>Mention ");
394 RenderUtils.render(selection, builder);
395 builder.append("</strong>");
396 final List<URI> entityURIs = selectedMention.get(KS.REFERS_TO, URI.class);
397 if (!entityURIs.isEmpty()) {
398 builder.append(" ➟ <strong>")
399 .append(entityURIs.size() == 1 ? "Entity" : "Entities")
400 .append("</strong>");
401 for (final URI entityURI : entityURIs) {
402 builder.append(" <strong>");
403 RenderUtils.render(entityURI, builder);
404 builder.append("</strong> <a href=\"#\" onclick=\"select('")
405 .append(RenderUtils.escapeJavaScriptString(entityURI)).append("')\">(select)</a>");
406 }
407 }
408 model.put("resourceDetailsTitle", builder.toString());
409 model.put("resourceDetailsBody", RenderUtils.render(selectedMention));
410
411 } else {
412
413 model.put("resourceDetailsTitle", "<strong>Resource metadata</strong>");
414 model.put("resourceDetailsBody", RenderUtils.render(resource));
415 }
416
417
418 return true;
419 }
420
421 private boolean uiLookupMention(final Map<String, Object> model, final URI mentionID,
422 final int limit) throws Throwable {
423
424
425 final Record mention = getRecord(KS.MENTION, mentionID);
426 if (mention == null) {
427 return false;
428 }
429
430
431 model.put("mention", Boolean.TRUE);
432
433
434 model.put("mentionData", RenderUtils.render(mention));
435
436
437 final URI resourceID = mention.getUnique(KS.MENTION_OF, URI.class, null);
438 if (resourceID != null) {
439 model.put("mentionResourceLink",
440 RenderUtils.render(resourceID, mentionID, new StringBuilder()).toString());
441 final Representation representation = getRepresentation(resourceID);
442 if (representation == null) {
443 model.put("mentionResourceExcerpt", "RESOURCE CONTENT NOT AVAILABLE");
444 } else {
445 final String text = representation.writeToString();
446 model.put("mentionResourceExcerpt", RenderUtils.renderText(text,
447 ImmutableList.of(mention), null, false, true, getUIConfig(),
448 new StringBuilder()));
449 }
450 }
451
452
453 final List<URI> entityIDs = mention.get(KS.REFERS_TO, URI.class);
454 if (!entityIDs.isEmpty()) {
455
456
457 final URI entityID = entityIDs.iterator().next();
458 final List<BindingSet> describeTriples = getEntityDescribeTriples(entityID, limit);
459 final int total = describeTriples.size() < limit ? describeTriples.size()
460 : countEntityDescribeTriples(entityID);
461 model.put("mentionEntityTriplesShown", describeTriples.size());
462 model.put("mentionEntityTriplesTotal", total);
463 model.put("mentionEntityTriples", String.join("", RenderUtils.renderSolutionTable(
464 ImmutableList.of("subject", "predicate", "object", "graph"), describeTriples)));
465
466
467 if (entityIDs.size() == 1) {
468 model.put("mentionEntityLink", RenderUtils.render(entityID));
469 } else {
470 final StringBuilder builder = new StringBuilder();
471 for (final URI id : entityIDs) {
472 builder.append(builder.length() > 0 ? " " : "");
473 RenderUtils.render(id, builder);
474 }
475 model.put("mentionEntityLinks", builder.toString());
476 }
477 }
478
479
480 return true;
481 }
482
483 private boolean uiLookupEntity(final Map<String, Object> model, final URI entityID,
484 final int limit) throws Throwable {
485
486
487 final List<BindingSet> describeTriples = getEntityDescribeTriples(entityID, limit);
488 final List<BindingSet> graphTriples = getEntityGraphTriples(entityID, limit);
489 if (describeTriples.isEmpty() && graphTriples.isEmpty()) {
490 return false;
491 }
492
493
494 model.put("entity", Boolean.TRUE);
495
496
497 if (!describeTriples.isEmpty()) {
498 final int total = describeTriples.size() < limit ? describeTriples.size()
499 : countEntityDescribeTriples(entityID);
500 model.put("entityTriplesShown", describeTriples.size());
501 model.put("entityTriplesTotal", total);
502 model.put("entityTriples", String.join("", RenderUtils.renderSolutionTable(
503 ImmutableList.of("subject", "predicate", "object", "graph"), describeTriples)));
504 }
505
506
507 if (!graphTriples.isEmpty()) {
508 final int total = graphTriples.size() < limit ? graphTriples.size()
509 : countEntityGraphTriples(entityID);
510 model.put("entityGraphShown", graphTriples.size());
511 model.put("entityGraphTotal", total);
512 model.put("entityGraph", String.join("", RenderUtils.renderSolutionTable(
513 ImmutableList.of("subject", "predicate", "object"), graphTriples)));
514 }
515
516
517 final List<Record> resources = getEntityResources(entityID, getUIConfig().getResultLimit());
518 if (!resources.isEmpty()) {
519
520
521 final int[] counts = countEntityResourcesAndMentions(entityID);
522 model.put("entityResourcesShown", resources.size());
523 model.put("entityResourcesCount", counts[0]);
524 model.put("entityMentionsCount", counts[1]);
525
526
527 final StringBuilder builder = new StringBuilder();
528 final List<URI> overviewProperties = getUIConfig().getResourceOverviewProperties();
529 final int width = 75 / (overviewProperties.size() + 2);
530 final String th = "<th style=\"width: " + width + "%\">";
531 builder.append("<table class=\"sparql table table-condensed tablesorter\"><thead>\n");
532 builder.append("<tr>").append(th).append("resource ID</th>");
533 for (final URI property : overviewProperties) {
534 builder.append(th)
535 .append(RenderUtils.escapeHtml(Data.toString(property,
536 Data.getNamespaceMap()))).append("</th>");
537 }
538 builder.append(th);
539 if (resources.size() < getUIConfig().getResultLimit()) {
540 builder.append("# mentions");
541 } else {
542 builder.append("<span title=\"Number of mentions per resource may be lower than "
543 + "the exact value as only a subset of all the entity mentions has been "
544 + "considered for building this page\"># mentions (truncated)</title>");
545 }
546 builder.append("</th></tr>\n</thead><tbody>\n");
547 for (final Record resource : resources) {
548 builder.append("<tr><td>");
549 RenderUtils.render(resource.getID(), entityID, builder);
550 for (final URI property : overviewProperties) {
551 builder.append("</td><td>");
552 RenderUtils.render(resource.get(property), builder);
553 }
554 builder.append("</td><td>");
555 RenderUtils.render(resource.getUnique(NUM_MENTIONS, Integer.class, null), builder);
556 builder.append("</td></tr>\n");
557 }
558 builder.append("</tbody></table>");
559 model.put("entityResources", builder.toString());
560 }
561
562
563 return true;
564 }
565
566 private void uiReportEntityMentions(final Map<String, Object> model,
567 @Nullable final URI entityID, @Nullable final URI property,
568 @Nullable final Value value, final int limit) throws Throwable {
569
570
571 if (entityID == null) {
572 return;
573 }
574
575
576 int numMentions = 0;
577 final List<Record> mentions = Lists.newArrayList();
578 for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
579 if (property == null || !mention.isNull(property)
580 && (value == null || mention.get(property).contains(value))) {
581 ++numMentions;
582 if (mentions.size() < limit) {
583 mentions.add(mention);
584 }
585 }
586 }
587
588
589 model.put("message", mentions.size() + " mentions shown out of " + numMentions);
590 model.put("mentionTable",
591 RenderUtils.renderRecordsTable(new StringBuilder(), mentions, null, null));
592 }
593
594 private void uiReportEntityMentionsAggregate(final Map<String, Object> model,
595 final URI entityID) throws Throwable {
596
597
598 if (entityID == null) {
599 return;
600 }
601
602
603 final Stream<Record> mentions = getEntityMentions(entityID, Integer.MAX_VALUE, null);
604 final Predicate<URI> filter = Predicates.not(Predicates.in(ImmutableSet.<URI>of(
605 NIF.BEGIN_INDEX, NIF.END_INDEX, KS.MENTION_OF)));
606 final String linkTemplate = "ui?action=entity-mentions&entity="
607 + UrlEscapers.urlFormParameterEscaper().escape(entityID.stringValue())
608 + "&property=${property}&value=${value}";
609 model.put("propertyValuesTable", RenderUtils.renderRecordsAggregateTable(
610 new StringBuilder(), mentions, filter, linkTemplate, null));
611 }
612
613 private void uiReportMentionValueOccurrences(final Map<String, Object> model,
614 final URI entityID, @Nullable final URI property) throws Throwable {
615
616
617 if (entityID == null || property == null) {
618 return;
619 }
620
621
622 final Multiset<Value> propertyValues = HashMultiset.create();
623 for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
624 propertyValues.addAll(mention.get(property, Value.class));
625 }
626
627
628 final Escaper esc = UrlEscapers.urlFormParameterEscaper();
629 final String linkTemplate = "ui?action=entity-mentions&entity="
630 + esc.escape(entityID.stringValue()) + "&property="
631 + esc.escape(Data.toString(property, Data.getNamespaceMap()))
632 + "&value=${element}";
633 model.put("valueOccurrencesTable", RenderUtils.renderMultisetTable(new StringBuilder(),
634 propertyValues, "Property value", "# Mentions", linkTemplate));
635 }
636
637 private void uiReportMentionPropertyOccurrences(final Map<String, Object> model,
638 final URI entityID) throws Throwable {
639
640
641 if (entityID == null) {
642 return;
643 }
644
645
646 final Multiset<URI> propertyURIs = HashMultiset.create();
647 for (final Record mention : getEntityMentions(entityID, Integer.MAX_VALUE, null)) {
648 propertyURIs.addAll(mention.getProperties());
649 }
650
651
652 final Escaper esc = UrlEscapers.urlFormParameterEscaper();
653 final String linkTemplate = "ui?action=entity-mentions&entity="
654 + esc.escape(entityID.stringValue()) + "&property=${element}";
655 model.put("propertyOccurrencesTable", RenderUtils.renderMultisetTable(new StringBuilder(),
656 propertyURIs, "Property", "# Mentions", linkTemplate));
657 }
658
659
660
661 @Nullable
662 private Record getRecord(final URI layer, @Nullable final URI id) throws Throwable {
663 final Record record = id == null ? null : getSession().retrieve(layer).ids(id).exec()
664 .getUnique();
665 if (record != null && layer.equals(KS.MENTION)) {
666 final String template = "SELECT ?e WHERE { ?e $$ $$ "
667 + (getUIConfig().isDenotedByAllowsGraphs() ? ""
668 : "FILTER NOT EXISTS { GRAPH ?e { ?s ?p ?o } } ") + "}";
669 for (final URI entityID : getSession()
670 .sparql(template, getUIConfig().getDenotedByProperty(), id).execTuples()
671 .transform(URI.class, true, "e")) {
672 record.add(KS.REFERS_TO, entityID);
673 }
674 }
675 return record;
676 }
677
678 @Nullable
679 private Representation getRepresentation(@Nullable final URI resourceID) throws Throwable {
680 final Representation representation = resourceID == null ? null : getSession().download(
681 resourceID).exec();
682 if (representation != null) {
683 closeOnCompletion(representation);
684 }
685 return representation;
686 }
687
688 private List<Record> getResourceMentions(final URI resourceID) throws Throwable {
689
690 final Record resource;
691 resource = getSession().retrieve(KS.RESOURCE).ids(resourceID).exec().getUnique();
692 if (resource == null) {
693 return Collections.emptyList();
694 }
695
696 final Map<URI, Record> mentions = Maps.newHashMap();
697 final List<URI> mentionIDs = resource.get(KS.HAS_MENTION, URI.class);
698 if (mentionIDs.isEmpty()) {
699 return Collections.emptyList();
700 }
701
702 for (final Record mention : getSession().retrieve(KS.MENTION).ids(mentionIDs).exec()) {
703 mentions.put(mention.getID(), mention);
704 }
705
706 final Set<URI> entityIDs = Sets.newHashSet();
707 for (final Record mention : mentions.values()) {
708 for (final URI entityID : mention.get(KS.REFERS_TO, URI.class)) {
709 entityIDs.add(entityID);
710 }
711 }
712
713 for (final List<URI> ids : Stream.create(mentionIDs).chunk(128)) {
714 final StringBuilder builder = new StringBuilder();
715 builder.append("SELECT ?m ?e WHERE { ?e ");
716 builder.append(Data.toString(getUIConfig().getDenotedByProperty(), null));
717 builder.append(" ?m VALUES ?m {");
718 for (final URI mentionID : ids) {
719 builder.append(' ').append(Data.toString(mentionID, null));
720 }
721 builder.append(" } ");
722 if (!getUIConfig().isDenotedByAllowsGraphs()) {
723 builder.append("FILTER NOT EXISTS { GRAPH ?e { ?s ?p ?o } } ");
724 }
725 builder.append("}");
726 for (final BindingSet bindings : getSession().sparql(builder.toString()).execTuples()) {
727 final URI mentionID = (URI) bindings.getValue("m");
728 final URI entityID = (URI) bindings.getValue("e");
729 Record record = mentions.get(mentionID);
730
731
732
733 record.add(KS.REFERS_TO, entityID);
734 entityIDs.add(entityID);
735 }
736 }
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768 final List<Record> sortedMentions = Lists.newArrayList(mentions.values());
769 Collections.sort(sortedMentions, new Comparator<Record>() {
770
771 @Override
772 public int compare(final Record r1, final Record r2) {
773 final int begin1 = r1.getUnique(NIF.BEGIN_INDEX, Integer.class, 0);
774 final int begin2 = r2.getUnique(NIF.BEGIN_INDEX, Integer.class, 0);
775 int result = Integer.compare(begin1, begin2);
776 if (result == 0) {
777 final int end1 = r1.getUnique(NIF.END_INDEX, Integer.class, Integer.MAX_VALUE);
778 final int end2 = r2.getUnique(NIF.END_INDEX, Integer.class, Integer.MAX_VALUE);
779 result = Integer.compare(end1, end2);
780 }
781 return result;
782 }
783
784 });
785 return sortedMentions;
786 }
787
788 private Stream<Record> getEntityMentions(final URI entityID, final int maxResults,
789 @Nullable final int[] numMentions) throws Throwable {
790
791
792 final List<URI> mentionURIs = getSession()
793 .sparql("SELECT ?m WHERE { $$ $$ ?m}", entityID,
794 getUIConfig().getDenotedByProperty()).execTuples()
795 .transform(URI.class, true, "m").toList();
796
797
798 if (numMentions != null) {
799 numMentions[0] = mentionURIs.size();
800 }
801
802
803 return getSession().retrieve(KS.MENTION).limit((long) maxResults).ids(mentionURIs).exec();
804 }
805
806 private List<Record> getEntityResources(final URI entityID, final int maxResults)
807 throws Throwable {
808
809
810 final Multiset<URI> resourceIDs = HashMultiset.create();
811 try (Stream<URI> stream = getSession()
812 .sparql("SELECT ?m WHERE { $$ $$ ?m }", entityID,
813 getUIConfig().getDenotedByProperty()).execTuples()
814 .transform(URI.class, true, "m")) {
815 for (final URI mentionID : stream) {
816 final String string = mentionID.stringValue();
817 final int index = string.indexOf("#");
818 if (index > 0) {
819 final URI resourceID = Data.getValueFactory().createURI(
820 string.substring(0, index));
821 if (resourceIDs.elementSet().size() == maxResults
822 && !resourceIDs.contains(resourceID)) {
823 break;
824 }
825 resourceIDs.add(resourceID);
826 }
827 }
828 }
829
830
831 final List<Record> resources;
832 resources = getSession().retrieve(KS.RESOURCE).ids(resourceIDs).exec().toList();
833 for (final Record resource : resources) {
834 resource.set(NUM_MENTIONS, resourceIDs.count(resource.getID()));
835 }
836 return resources;
837 }
838
839 private List<BindingSet> getEntityDescribeTriples(final URI entityID, final int limit)
840 throws Throwable {
841 return getSession()
842 .sparql("SELECT (COALESCE(?s, $$) AS ?subject) ?predicate "
843 + "(COALESCE(?o, $$) AS ?object) ?graph "
844 + "WHERE { { GRAPH ?graph { $$ ?predicate ?o } } UNION "
845 + "{ GRAPH ?graph { ?s ?predicate $$ } } } LIMIT $$", entityID, entityID,
846 entityID, entityID, limit).execTuples().toList();
847 }
848
849 private List<BindingSet> getEntityGraphTriples(final URI entityID, final int limit)
850 throws Throwable {
851 return getSession()
852 .sparql("SELECT ?subject ?predicate ?object "
853 + "WHERE { GRAPH $$ { ?subject ?predicate ?object } } LIMIT $$", entityID,
854 limit).execTuples().toList();
855 }
856
857 private int countEntityDescribeTriples(final URI entityID) throws Throwable {
858 return getSession()
859 .sparql("SELECT (COUNT(*) AS ?n) "
860 + "WHERE { { GRAPH ?g { $$ ?p ?o } } UNION { GRAPH ?g { ?s ?p $$ } } }",
861 entityID, entityID).execTuples().transform(Integer.class, true, "n")
862 .getUnique();
863 }
864
865 private int countEntityGraphTriples(final URI entityID) throws Throwable {
866 return getSession()
867 .sparql("SELECT (COUNT(*) AS ?n) WHERE { GRAPH $$ { ?s ?p ?o } }", entityID)
868 .execTuples().transform(Integer.class, true, "n").getUnique();
869 }
870
871 private int[] countEntityResourcesAndMentions(final URI entityID) throws Throwable {
872 final BindingSet tuple = getSession()
873 .sparql("SELECT (COUNT(DISTINCT ?r) AS ?nr) (COUNT(*) AS ?nm) "
874 + "WHERE { $$ $$ ?m . BIND(IRI(STRBEFORE(STR(?m), \"#\")) AS ?r) }",
875 entityID, getUIConfig().getDenotedByProperty()).execTuples().getUnique();
876 return new int[] { ((Literal) tuple.getValue("nr")).intValue(),
877 ((Literal) tuple.getValue("nm")).intValue() };
878 }
879
880 private Stream<BindingSet> sendQuery(final String query, final Long timeout) throws Throwable {
881
882 final String form = RDFUtil.detectSparqlForm(query);
883 if (form.equalsIgnoreCase("select")) {
884 return closeOnCompletion(getSession().sparql(query).timeout(timeout).execTuples());
885
886 } else if (form.equalsIgnoreCase("construct") || form.equals("describe")) {
887 final List<String> variables = ImmutableList.of("subject", "predicate", "object");
888 final Function<Statement, BindingSet> transformer = new Function<Statement, BindingSet>() {
889
890 @Override
891 public BindingSet apply(final Statement statement) {
892 return new ListBindingSet(variables, statement.getSubject(),
893 statement.getPredicate(), statement.getObject());
894 }
895
896 };
897 final Stream<BindingSet> stream = getSession().sparql(query).timeout(timeout)
898 .execTriples().transform(transformer, 1);
899 stream.setProperty("variables", variables);
900 return closeOnCompletion(stream);
901
902 } else {
903 final boolean result = getSession().sparql(query).timeout(timeout).execBoolean();
904 final List<String> variables = ImmutableList.of("result");
905 final BindingSet bindings = new ListBindingSet(variables,
906 BooleanLiteralImpl.valueOf(result));
907 return closeOnCompletion(Stream.create(new BindingSet[] { bindings }).setProperty(
908 "variables", variables));
909 }
910 }
911
912 }