Java tutorial
/** * Copyright (c) 2011-2014 INRIA. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> **/ package fr.inria.eventcloud.api; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Arrays; import java.util.regex.Pattern; import org.apache.jena.riot.Lang; import org.objectweb.proactive.extensions.p2p.structured.utils.StringRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Charsets; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.primitives.Longs; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.graph.NodeFactory; import com.hp.hpl.jena.graph.Node_ANY; import com.hp.hpl.jena.graph.Node_Blank; import com.hp.hpl.jena.graph.Triple; import fr.inria.eventcloud.utils.NodeSerializer; /** * A quadruple is a 4-tuple containing respectively a graph, a subject, a * predicate and an object value. The object value can be either an IRI or a * Literal whereas for all the others elements an IRI is required. * <p> * Jena already provides its own abstraction for quadruples. However, we have to * impose some restrictions on the values of a quadruple. For example we do not * allow Blank Nodes. By providing our own quadruple abstraction, we can check * for this kind of rule when the object is created. Moreover, the quadruple * class provides a constructor that use Jena objects for type-checking at * compile time. However, these objects are not serializable, that's why the * quadruple abstraction overrides the readObject and writeObject methods. * <p> * Such a quadruple can be published and handled as an event. It embeds some * meta information such as the publication time that indicates when the event * has been published and optionally the source. * * @author lpellegr */ public class Quadruple implements Externalizable, Event { private static final long serialVersionUID = 160L; private static final Logger LOG = LoggerFactory.getLogger(Quadruple.class); public static final String PUBLICATION_TIME_SEPARATOR = "$$"; public static final String PUBLICATION_SOURCE_SEPARATOR = "@@"; // contains respectively the graph, the subject, the predicate // and the object value of the quadruple protected transient Node[] nodes; protected transient long publicationTime; protected transient String publicationSource; /** * Cached nodes hashCode (field is immutable) */ private transient int nodesHashCode; /** * Defines the different formats that are allowed to read or write * quadruples from and to an input stream. */ public enum SerializationFormat { NQuads, TriG; public Lang toJenaLang() { if (super.ordinal() == 0) { return Lang.NQUADS; } else { return Lang.TRIG; } } } /** * Constructs a new Quadruple with the specified {@code graph} node and the * given {@code triple}. * * @param graph * the graph value. * @param triple * the triple to use in order to extract the subject, the * predicate and the object. * * @throws IllegalArgumentException * if the type of one element is not allowed (e.g. blank node, * variable or {@code null} value). */ public Quadruple(Node graph, Triple triple) { this(graph, triple.getSubject(), triple.getPredicate(), triple.getObject()); } /** * Constructs a new Quadruple with the specified {@code graph}, * {@code subject}, {@code predicate} and {@code object} nodes. This * constructor will check the type of each node and throw an * {@link IllegalArgumentException} if the type of one element is not * allowed. * * @param graph * the graph value. * @param subject * the subject value. * @param predicate * the predicate value. * @param object * the object value. * * @throws IllegalArgumentException * if the type of one element is not allowed (e.g. blank node, * variable or {@code null} value). */ public Quadruple(Node graph, Node subject, Node predicate, Node object) { this(graph, subject, predicate, object, true, true); } /** * Creates a new Quadruple with the specified {@code graph}, {@code subject} * , {@code predicate} and {@code object}. This constructor allows to skip * the type checking operation. <strong>It must be used with care because it * is possible to create quadruples which are not supported by the * system</strong>. * * @param graph * the graph value. * @param subject * the subject value. * @param predicate * the predicate value. * @param object * the object value. * @param checkType * indicates whether the type of each element has to be check or * not. * @param parseMetaInformation * indicates whether the graph value has to be parsed or not. */ public Quadruple(Node graph, Node subject, Node predicate, Node object, boolean checkType, boolean parseMetaInformation) { this(); if (checkType) { isAllowed(graph); isAllowed(subject); isAllowed(predicate); isAllowed(object); } if (parseMetaInformation) { this.nodes[0] = this.extractAndSetMetaInformation(graph); } else { this.nodes[0] = graph; } this.nodes[1] = subject; this.nodes[2] = predicate; this.nodes[3] = object; } public Quadruple() { this.nodes = new Node[4]; this.publicationTime = -1; } private static final void isAllowed(Node node) { if (node == null) { throw new IllegalArgumentException("The specified node value is null"); } if (node instanceof Node_Blank) { throw new IllegalArgumentException("Blank nodes are not supported: " + node.toString()); } if (node instanceof Node_ANY) { throw new IllegalArgumentException( "Variables are not allowed in a quadruple (see QuadruplePattern): " + node.toString()); } } /** * Returns the graph value. * * @return the graph value. */ public final Node getGraph() { return this.nodes[0]; } /** * Returns the subject value. * * @return the subject value. */ public final Node getSubject() { return this.nodes[1]; } /** * Returns the predicate value. * * @return the predicate value. */ public final Node getPredicate() { return this.nodes[2]; } /** * Returns the object value. * * @return the object value. */ public final Node getObject() { return this.nodes[3]; } /** * Returns the RDF term found at the specified {@code index}. A quadruple * embeds only four RDF terms. The index value starts at 0. The terms are * stored in the following order: graph, subject, predicate and object. * * @param index * the index to use for retrieving an RDF term contained by the * quadruple. * * @return the RDF term found at the specified {@code index}. */ public final Node getTermByIndex(int index) { if (index < 0 || index > 3) { throw new IllegalArgumentException("Invalid index: " + index); } return this.nodes[index]; } /** * Returns a 128 bits hash value for the current quadruple. * * @return a 128 bits hash value for the current quadruple. */ public HashCode hashValue() { Hasher hasher = Hashing.murmur3_128().newHasher(); for (int i = 0; i < this.nodes.length; i++) { hasher.putString(this.nodes[i].toString(), Charsets.UTF_8); } if (this.publicationSource != null) { hasher.putUnencodedChars(this.publicationSource); } hasher.putLong(this.publicationTime); return hasher.hash(); } /** * {@inheritDoc} */ @Override public int hashCode() { if (this.nodesHashCode == 0) { this.nodesHashCode = 31 + Arrays.hashCode(this.nodes); } int hash = this.nodesHashCode; hash = hash * 31 + Longs.hashCode(this.publicationTime); if (this.publicationSource != null) { hash = hash * 31 + this.publicationSource.hashCode(); } return hash; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Quadruple) { Quadruple other = (Quadruple) obj; for (int i = 0; i < this.nodes.length; i++) { if (!this.nodes[i].equals(other.nodes[i])) { return false; } } if (!equalsNull(this.publicationSource, other.publicationSource)) { return false; } if (this.publicationTime != other.publicationTime) { return false; } return true; } return false; } private static final boolean equalsNull(String s1, String s2) { if (s1 == s2) { return true; } if (s1 != null && s2 != null) { return s1.equals(s2); } return false; } /** * Returns the quadruple as an array of {@link Node}s. * * @return the quadruple as an array of {@link Node}s. The array contains * respectively the graph, the subject, the predicate, and the * object value. */ public Node[] toArray() { return Arrays.copyOf(this.nodes, this.nodes.length); } /** * Returns the {@link Triple} value associated to the quadruple by cutting * the graph value. * * @return the {@link Triple} value associated to the quadruple by cutting * the graph value. */ public Triple toTriple() { return Triple.create(this.nodes[1], this.nodes[2], this.nodes[3]); } /** * Creates a new node containing the meta information associated to the * quadruple. This new node is the concatenation of the original graph value * and the meta information (e.g. the publication time, the publication * source, etc.). * * @return a new node for the graph value whose the content is equals to the * concatenation of the original graph value and the meta * information. */ public Node createMetaGraphNode() { if (this.nodes[0].isURI()) { StringBuilder uri = new StringBuilder(); uri.append(this.nodes[0].getURI()); boolean isPublicationTimeSet = this.publicationTime != -1; boolean isPublicationSourceSet = this.publicationSource != null; if (isPublicationTimeSet) { uri.append(PUBLICATION_TIME_SEPARATOR); // adds the publication time in the first position uri.append(this.publicationTime); } if (isPublicationSourceSet) { uri.append(PUBLICATION_SOURCE_SEPARATOR); // adds the publication source in the second position uri.append(this.publicationSource); } return NodeFactory.createURI(uri.toString()); } return this.nodes[0]; } /** * Returns a timestamp indicating when the quadruple has been published or * {@code -1} if the quadruple has not been yet published. * * @return a timestamp indicating when the quadruple has been published or * {@code -1} if the quadruple has not been yet published. */ public long getPublicationTime() { return this.publicationTime; } /** * Sets the publication time of the quadruple to the current time when the * method call is performed. This is strictly equivalent to * {@code setPublicationTime(System.currentTimeMillis())}. */ public void setPublicationTime() { this.setPublicationTime(System.currentTimeMillis()); } /** * Sets the publication time of the quadruple. The publication time is * assumed to be a Java timestamp (with millisecond precision) retrieved by * calling for example {@link System#currentTimeMillis()}. * * @param publicationTime * the time value to use in order to timestamp this quadruple. */ public void setPublicationTime(long publicationTime) { if (publicationTime < 1) { throw new IllegalArgumentException( "Expected publication datetime greater than 0 but was: " + publicationTime); } if (this.publicationTime > 0) { LOG.warn("Publication time {} overriden by {}", this.publicationTime, publicationTime); } synchronized (this) { this.publicationTime = publicationTime; } } /** * Returns an URL representing the endpoint of the publisher which has * published the quadruple, or {@code null}. * * @return an URL representing the endpoint of the publisher which has * published the quadruple, or {@code null}. */ public String getPublicationSource() { return this.publicationSource; } /** * Sets the publication source of the quadruple. The source is assumed to be * an URL representing the endpoint of the publisher. * * @param source * an URL representing the endpoint of the publisher. */ public void setPublicationSource(String source) { if (source == null) { throw new IllegalArgumentException("Invalid source: " + source); } if (this.publicationSource != null) { LOG.warn("Publication source '{}' overriden by '{}'", this.publicationSource, source); } synchronized (this) { this.publicationSource = source; } } private final boolean metaInformationSet() { return this.publicationTime > 0 || this.publicationSource != null; } protected final Node extractAndSetMetaInformation(Node graph) { MetaGraph metaGraph = parse(graph); if (metaGraph != null) { if (metaGraph.publicationTime != -1) { this.setPublicationTime(metaGraph.publicationTime); } if (metaGraph.publicationSource != null) { this.setPublicationSource(metaGraph.publicationSource); } // returns a graph value without any meta information return NodeFactory.createURI(metaGraph.graph); } return graph; } private static MetaGraph parse(Node graph) { if (graph.isURI()) { String uri = graph.getURI(); return parse(uri); } // return null and do not thrown an exception because this method may be // invoked during the deserialization of a QuadruplePattern. In such a // case the graph may not be an URI and the null value is useful on high // level to detect that no meta information has been parsed return null; } private static MetaGraph parse(String graph) { int publicationTimeSeparatorIndex = graph.lastIndexOf(PUBLICATION_TIME_SEPARATOR); int publicationSourceSeparatorIndex = graph.lastIndexOf(PUBLICATION_SOURCE_SEPARATOR); if (publicationTimeSeparatorIndex == -1 && publicationSourceSeparatorIndex == -1) { return null; } else if (publicationTimeSeparatorIndex >= 0 && publicationSourceSeparatorIndex == -1) { String publicationTime = graph .substring(publicationTimeSeparatorIndex + PUBLICATION_TIME_SEPARATOR.length()); try { return new MetaGraph(graph.substring(0, publicationTimeSeparatorIndex), Long.parseLong(publicationTime), null); } catch (NumberFormatException e) { throw new IllegalStateException("Invalid publication time: " + publicationTime); } } else if (publicationTimeSeparatorIndex == -1 && publicationSourceSeparatorIndex >= 0) { return new MetaGraph(graph.substring(0, publicationSourceSeparatorIndex), -1, graph.substring(publicationSourceSeparatorIndex + PUBLICATION_SOURCE_SEPARATOR.length())); } else { String publicationTime = graph.substring( publicationTimeSeparatorIndex + PUBLICATION_TIME_SEPARATOR.length(), publicationSourceSeparatorIndex); // if publication time and source are specified, they are // necessarily in the order publicationTime, publicationSource try { return new MetaGraph(graph.substring(0, publicationTimeSeparatorIndex), Long.parseLong(publicationTime), graph.substring(publicationSourceSeparatorIndex + PUBLICATION_SOURCE_SEPARATOR.length())); } catch (NumberFormatException e) { throw new IllegalStateException("Invalid publication time: " + publicationTime); } } } private static class MetaGraph { protected final String graph; protected final long publicationTime; protected final String publicationSource; public MetaGraph(String graph, long publicationTime, String publicationSource) { this.graph = graph; this.publicationTime = publicationTime; this.publicationSource = publicationSource; } } public static Node removeMetaInformation(Node graph) { return NodeFactory.createURI(removeMetaInformation(graph.getURI())); } public static String removeMetaInformation(String graph) { String[] splits = graph.split(Pattern.quote(PUBLICATION_TIME_SEPARATOR)); if (splits.length == 1) { return graph; } else { return splits[0]; } } /** * Returns the publication time associated to the specified * {@code metaGraphNode} or {@code -1} if the publication time is not * defined or if the specified node is not a meta graph node. * * @param metaGraphNode * the meta graph node to parse. * * @return the publication time associated to the specified * {@code metaGraphNode} or {@code -1} if the publication time is * not defined or if the specified node is not a meta graph node. */ public static final long getPublicationTime(Node metaGraphNode) { MetaGraph metaGraph = parse(metaGraphNode); if (metaGraph != null) { return metaGraph.publicationTime; } return -1; } /** * Returns the publication time associated to the specified * {@code metaGraphNode} or {@code -1} if the publication time is not * defined or if the specified node is not a meta graph node. * * @param metaGraphValue * the meta graph value to parse. * * @return the publication time associated to the specified * {@code metaGraphNode} or {@code -1} if the publication time is * not defined or if the specified node is not a meta graph node. */ public static final long getPublicationTime(String metaGraphValue) { MetaGraph metaGraph = parse(metaGraphValue); if (metaGraph != null) { return metaGraph.publicationTime; } return -1; } /** * Returns the publication source associated to the specified * {@code metaGraphNode} or {@code null} if the publication source is not * defined or if the specified node is not a meta graph node. * * @param metaGraphNode * the meta graph node to parse. * * @return the publication source associated to the specified * {@code metaGraphNode} or {@code null} if the publication source * is not defined or if the specified node is not a meta graph node. */ public static final String getPublicationSource(Node metaGraphNode) { MetaGraph metaGraph = parse(metaGraphNode); if (metaGraph != null) { return metaGraph.publicationSource; } return null; } /** * Returns the publication source associated to the specified * {@code metaGraphNode} or {@code null} if the publication source is not * defined or if the specified node is not a meta graph node. * * @param metaGraphValue * the meta graph value to parse. * * @return the publication source associated to the specified * {@code metaGraphNode} or {@code null} if the publication source * is not defined or if the specified node is not a meta graph node. */ public static final String getPublicationSource(String metaGraphValue) { MetaGraph metaGraph = parse(metaGraphValue); if (metaGraph != null) { return metaGraph.publicationSource; } return null; } /** * Returns a boolean indicating whether the specified {@code node} is a meta * graph node (a node containing meta information about a quadruple) or not. * * @param node * the node to check. * * @return a boolean indicating whether the specified {@code node} is a meta * graph node (a node containing meta information about a quadruple) * or not. */ public static boolean isMetaGraphNode(Node node) { return parse(node) != null; } /** * {@inheritDoc} */ @Override public String toString() { return this.toString(StringRepresentation.STRING); } public String toString(StringRepresentation representation) { StringBuilder result = new StringBuilder("("); for (int i = 0; i < this.nodes.length; i++) { result.append(format(this.nodes[i], representation)); if (i < this.nodes.length - 1) { result.append(", "); } } result.append(')'); if (this.metaInformationSet()) { result.append('{'); if (this.publicationTime > 0) { result.append(this.publicationTime); } if (this.publicationSource != null) { result.append(", "); result.append(this.publicationSource); } result.append('}'); } return result.toString(); } private static String format(Node node, StringRepresentation representation) { if (node == Node.ANY) { return "?"; } return representation.apply(node.toString()); } /** * {@inheritDoc} */ @Override public void writeExternal(ObjectOutput out) throws IOException { // graph, subject and predicate are necessarily IRI values String graph = this.createMetaGraphNode().getURI(); String subject = this.nodes[1].toString(); String predicate = this.nodes[2].toString(); StringBuilder gsp = new StringBuilder(graph); gsp.append(' '); gsp.append(subject); gsp.append(' '); gsp.append(predicate); out.writeUTF(gsp.toString()); NodeSerializer.writeLiteralOrURI(out, this.nodes[3]); } /** * {@inheritDoc} */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { String[] chunks = in.readUTF().split(" "); this.nodes[0] = this.extractAndSetMetaInformation(NodeFactory.createURI(chunks[0])); this.nodes[1] = NodeFactory.createURI(chunks[1]); this.nodes[2] = NodeFactory.createURI(chunks[2]); this.nodes[3] = NodeSerializer.readLiteralOrURI(in); } }