All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.fbk.knowledgestore.server.http.jaxrs.RenderUtils Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.7.1
Show newest version
package eu.fbk.knowledgestore.server.http.jaxrs;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
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.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import com.google.common.net.UrlEscapers;

import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;

import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.server.http.UIConfig;
import eu.fbk.knowledgestore.vocabulary.KS;
import eu.fbk.knowledgestore.vocabulary.NIF;
import eu.fbk.knowledgestore.vocabulary.NWR;

/**
 * Collection of utility methods for rendering various kinds of object to HTML.
 */
public final class RenderUtils {

    private static final boolean CHAR_OFFSET_HACK = Boolean.parseBoolean(System.getProperty(
            "ks.charOffsetHack", "false"))
            || Boolean.parseBoolean(MoreObjects.firstNonNull(System.getenv("KS_CHAR_OFFSET_HACK"),
                    "false"));

    private static final AtomicInteger COUNTER = new AtomicInteger(0);

    /**
     * Render a generic object, returning the corresponding HTML string. Works for null objects,
     * RDF {@code Value}s, {@code Record}s, {@code BindingSet}s and {@code Iterable}s of the
     * former.
     *
     * @param object
     *            the object to render.
     * @return the rendered HTML string
     */
    public static String render(final Object object) {
        try {
            final StringBuilder builder = new StringBuilder();
            render(object, builder);
            return builder.toString();
        } catch (final IOException ex) {
            throw new Error(ex); // should not happen
        }
    }

    /**
     * Render a generic object, emitting the corresponding HTML string to the supplied
     * {@code Appendable} object. Works for null objects, RDF {@code Value}s, {@code Record}s,
     * {@code BindingSet}s and {@code Iterable}s of the former.
     *
     * @param object
     *            the object to render.
     */
    @SuppressWarnings("unchecked")
    public static  T render(final Object object, final T out)
            throws IOException {

        if (object instanceof URI) {
            render((URI) object, null, out);

        } else if (object instanceof Literal) {
            final Literal literal = (Literal) object;
            out.append("").append(literal.stringValue()).append("");

        } else if (object instanceof BNode) {
            final BNode bnode = (BNode) object;
            out.append("_:").append(bnode.getID());

        } else if (object instanceof Record) {
            final Record record = (Record) object;
            out.append("\n\n");
            for (final URI property : Ordering.from(Data.getTotalComparator()).sortedCopy(
                    record.getProperties())) {
                out.append("\n");
            }
            out.append("
ID"); render(record.getID(), out); out.append("
"); render(property, out); out.append(""); final List values = record.get(property); if (values.size() == 1) { render(values.get(0), out); } else { out.append("
"); String separator = ""; for (final Object value : Ordering.from(Data.getTotalComparator()).sortedCopy( record.get(property))) { out.append(separator); render(value, out); separator = "
"; } out.append("
"); } out.append("
"); } else if (object instanceof BindingSet) { render(ImmutableSet.of(object)); } else if (object instanceof Iterable) { final Iterable iterable = (Iterable) object; boolean isEmpty = true; boolean isIterableOfSolutions = true; for (final Object element : iterable) { isEmpty = false; if (!(element instanceof BindingSet)) { isIterableOfSolutions = false; break; } } if (!isEmpty) { if (!isIterableOfSolutions) { String separator = ""; for (final Object element : (Iterable) object) { out.append(separator); render(element, out); separator = "
"; } } else { Joiner.on("").appendTo(out, renderSolutionTable(null, (Iterable) object).iterator()); } } } else if (object != null) { out.append(object.toString()); } return out; } public static T render(final URI uri, @Nullable final URI selection, final T out) throws IOException { out.append("").append(RenderUtils.shortenURI(uri)).append(""); return out; } public static T renderText(final String text, final String contentType, final T out) throws IOException { if (contentType.equals("text/plain")) { out.append("
\n").append(RenderUtils.escapeHtml(text)) .append("\n
\n"); } else { // TODO: only XML enabled by default - should be generalized / made more robust out.append("
")
                    .append(RenderUtils.escapeHtml(text)).append("
"); } return out; } public static T renderText(final String text, final List mentions, @Nullable final URI selection, final boolean canSelect, final boolean onlyMention, final UIConfig config, final T out) throws IOException { final List lines = Lists.newArrayList(Splitter.on('\n').split(text)); if (CHAR_OFFSET_HACK) { for (int i = 0; i < lines.size(); ++i) { lines.set(i, lines.get(i).replaceAll("\\s+", " ") + " "); } } int lineStart = CHAR_OFFSET_HACK ? 0 : -1; int lineOffset = 0; int mentionIndex = 0; boolean anchorAdded = false; out.append("
\n"); for (final String l : lines) { final String line = CHAR_OFFSET_HACK ? l.trim() : l; lineStart += CHAR_OFFSET_HACK ? 0 : 1; boolean mentionFound = false; while (mentionIndex < mentions.size()) { final Record mention = mentions.get(mentionIndex); final Integer begin = mention.getUnique(NIF.BEGIN_INDEX, Integer.class); final Integer end = mention.getUnique(NIF.END_INDEX, Integer.class); String cssStyle = null; for (final UIConfig.Category category : config.getMentionCategories()) { if (category.getCondition().evalBoolean(mention)) { cssStyle = category.getStyle(); break; } } if (cssStyle == null || begin == null || end == null || begin < lineStart + lineOffset) { ++mentionIndex; continue; } if (end > lineStart + line.length()) { break; } final boolean selected = mention.getID().equals(selection) || mention.get(KS.REFERS_TO, URI.class).contains(selection); if (!mentionFound) { out.append("

"); } out.append(RenderUtils.escapeHtml(line.substring(lineOffset, begin - lineStart))); out.append(" values = mention.get(property, Value.class); if (!values.isEmpty()) { out.append(separator) .append(Data.toString(property, Data.getNamespaceMap())) .append(" = "); for (final Value value : values) { if (!KS.MENTION.equals(value) && !NWR.TIME_OR_EVENT_MENTION.equals(value) && !NWR.ENTITY_MENTION.equals(value)) { out.append(" ").append( Data.toString(value, Data.getNamespaceMap())); } } separator = "\n"; } } out.append("\">"); out.append(RenderUtils.escapeHtml(line.substring(begin - lineStart, end - lineStart))); out.append(""); lineOffset = end - lineStart; ++mentionIndex; mentionFound = true; } if (mentionFound || !onlyMention) { if (!mentionFound) { out.append("

\n"); } out.append(RenderUtils.escapeHtml(line.substring(lineOffset, line.length()))); out.append("

\n"); } lineStart += line.length(); lineOffset = 0; } out.append("
\n"); return out; } /** * Render in a streaming-way the solutions of a SPARQL SELECT query to an HTML table, emitting * an iterable with of HTML fragments. * * @param variables * the variables to render in the table, in the order they should be rendered; if * null, variables will be automatically extracted from the solutions and all the * variables in alphanumeric order will be emitted * @param solutions * the solutions to render */ public static Iterable renderSolutionTable(final List variables, final Iterable solutions) { final List actualVariables; if (variables != null) { actualVariables = ImmutableList.copyOf(variables); } else { final Set variableSet = Sets.newHashSet(); for (final BindingSet solution : solutions) { variableSet.addAll(solution.getBindingNames()); } actualVariables = Ordering.natural().sortedCopy(variableSet); } final int width = 75 / actualVariables.size(); final StringBuilder builder = new StringBuilder(); builder.append("\n"); for (final String variable : actualVariables) { builder.append(""); } final Iterable header = ImmutableList.of(builder.toString()); final Iterable footer = ImmutableList.of("
") .append(escapeHtml(variable)).append("
"); final Function renderer = new Function() { @Override public String apply(final BindingSet bindings) { if (Thread.interrupted()) { throw new IllegalStateException("Interrupted"); } final StringBuilder builder = new StringBuilder(); builder.append(""); for (final String variable : actualVariables) { builder.append(""); try { render(bindings.getValue(variable), builder); } catch (final IOException ex) { throw new Error(ex); } builder.append(""); } builder.append("\n"); return builder.toString(); } }; return Iterables.concat(header, Iterables.transform(solutions, renderer), footer); } public static T renderMultisetTable(final T out, final Multiset multiset, final String elementHeader, final String occurrencesHeader, @Nullable final String linkTemplate) throws IOException { final String tableID = "table" + COUNTER.getAndIncrement(); out.append("\n"); out.append("\n\n\n"); out.append("\n"); for (final Object element : multiset.elementSet()) { final int occurrences = multiset.count(element); out.append("\n"); } out.append("\n
").append(MoreObjects.firstNonNull(elementHeader, "Value")) .append("") .append(MoreObjects.firstNonNull(occurrencesHeader, "Occurrences")) .append("
"); RenderUtils.render(element, out); out.append(""); if (linkTemplate == null) { out.append(Integer.toString(occurrences)); } else { final Escaper esc = UrlEscapers.urlFormParameterEscaper(); final String e = esc.escape(Data.toString(element, Data.getNamespaceMap())); final String u = linkTemplate.replace("${element}", e); out.append("") .append(Integer.toString(occurrences)).append(""); } out.append("
\n"); out.append(""); return out; } public static T renderRecordsTable(final T out, final Iterable records, @Nullable List propertyURIs, @Nullable final String extraOptions) throws IOException { // Extract the properties to show if not explicitly supplied if (propertyURIs == null) { final Set uriSet = Sets.newHashSet(); for (final Record record : records) { uriSet.addAll(record.getProperties()); } propertyURIs = Ordering.from(Data.getTotalComparator()).sortedCopy(uriSet); } // Emit the table final String tableID = "table" + COUNTER.getAndIncrement(); out.append("\n"); out.append("\n"); for (final URI propertyURI : propertyURIs) { out.append(""); } out.append("\n\n\n"); for (final Record record : records) { out.append(""); for (final URI propertyURI : propertyURIs) { out.append(""); } out.append("\n"); } out.append("\n
URI").append(RenderUtils.shortenURI(propertyURI)).append("
").append(RenderUtils.render(record.getID())).append("").append(RenderUtils.render(record.get(propertyURI))) .append("
\n"); out.append(""); return out; } public static T renderRecordsAggregateTable(final T out, final Iterable records, @Nullable final Predicate propertyFilter, @Nullable final String linkTemplate, @Nullable final String extraOptions) throws IOException { // Aggregate properties and values final Map> properties = Maps.newHashMap(); final Map examples = Maps.newHashMap(); for (final Record record : records) { for (final URI property : record.getProperties()) { if (propertyFilter == null || propertyFilter.apply(property)) { Multiset values = properties.get(property); if (values == null) { values = HashMultiset.create(); properties.put(property, values); } for (final Value value : record.get(property, Value.class)) { values.add(value); examples.put(ImmutableList.of(property, value), record.getID()); } } } } // Emit the table final Ordering ordering = Ordering.from(Data.getTotalComparator()); final String tableID = "table" + COUNTER.getAndIncrement(); out.append("\n"); out.append("\n" + "\n\n"); out.append("\n"); for (final URI property : ordering.sortedCopy(properties.keySet())) { final Multiset values = properties.get(property); for (final Value value : ordering.sortedCopy(values.elementSet())) { final int occurrences = values.count(value); final URI example = examples.get(ImmutableList.of(property, value)); out.append("\n"); } } out.append("\n
PropertyValueOccurrencesExample
"); render(property, out); out.append(""); render(value, out); out.append(""); if (linkTemplate == null) { out.append(Integer.toString(occurrences)); } else { final Escaper e = UrlEscapers.urlFormParameterEscaper(); final String p = e.escape(Data.toString(property, Data.getNamespaceMap())); final String v = e.escape(Data.toString(value, Data.getNamespaceMap())); final String u = linkTemplate.replace("${property}", p).replace("${value}", v); out.append("") .append(Integer.toString(occurrences)).append(""); } out.append(""); render(example, out); out.append("
\n"); out.append(""); return out; } /** * Returns a shortened version of the supplied RDF {@code URI}. * * @param uri * the uri to shorten * @return the shortened URI string */ @Nullable public static String shortenURI(@Nullable final URI uri) { if (uri == null) { return null; } final String prefix = Data.namespaceToPrefix(uri.getNamespace(), Data.getNamespaceMap()); if (prefix != null) { return prefix + ':' + uri.getLocalName(); } final String ns = uri.getNamespace(); return "<.." + uri.stringValue().substring(ns.length() - 1) + ">"; // final int index = uri.stringValue().lastIndexOf('/'); // if (index >= 0) { // return "<.." + uri.stringValue().substring(index) + ">"; // } // return "<" + uri.stringValue() + ">"; } /** * Transforms the supplied object to an escaped HTML string. * * @param object * the object * @return the escaped HTML string */ @Nullable public static String escapeHtml(@Nullable final Object object) { return object == null ? null : HtmlEscapers.htmlEscaper().escape(object.toString()); } private RenderUtils() { } }