package won.protocol.message; import com.google.common.collect.Sets; import org.apache.jena.query.*; import org.apache.jena.rdf.model.*; import org.apache.jena.rdf.model.impl.ResourceImpl; import org.apache.jena.tdb.TDB; import org.apache.jena.vocabulary.RDF; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import won.protocol.util.RdfUtils; import won.protocol.vocabulary.RDFG; import won.protocol.vocabulary.WONMSG; import java.io.Serializable; import java.net.URI; import java.util.*; import java.util.stream.Collectors; /** * Wraps an RDF dataset representing a WoN message. * <p> * Note: this implementation is not thread-safe. */ public class WonMessage implements Serializable { final Logger logger = LoggerFactory.getLogger(getClass()); private Dataset messageContent; private Dataset completeDataset; //private Model messageMetadata; //private URI messageEventURI; private List<Model> envelopeGraphs; private List<String> envelopeGraphNames; private URI outerEnvelopeGraphURI; private Model outerEnvelopeGraph; private URI messageURI; private WonMessageType messageType; // ConnectMessage, CreateMessage, NeedStateMessage private WonMessageDirection envelopeType; private URI senderURI; private URI senderNeedURI; private URI senderNodeURI; private URI receiverURI; private URI receiverNeedURI; private URI receiverNodeURI; private List<URI> refersTo = new ArrayList<>(); private URI isResponseToMessageURI; private URI isRemoteResponseToMessageURI; private List<String> contentGraphNames; private WonMessageType isResponseToMessageType; private URI correspondingRemoteMessageURI; private List<AttachmentHolder> attachmentHolders; //private Resource msgBnode; // private Signature signature; public WonMessage(Dataset completeDataset) { this.completeDataset = completeDataset; } public static WonMessage deepCopy(WonMessage original) { return new WonMessage(RdfUtils.cloneDataset(original.completeDataset)); } public synchronized Dataset getCompleteDataset() { return RdfUtils.cloneDataset(this.completeDataset); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, RDFNode value) { if (logger.isDebugEnabled()) { logger.debug("adding property {}, value {}, to message {} in envelope {}", new Object[]{property, value, getMessageURI(), getOuterEnvelopeGraphURI()}); } getOuterEnvelopeGraph().getResource(getMessageURI().toString()).addProperty(property, value); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param uri the object of the property, assumed to be an uri */ public synchronized void addMessageProperty(Property property, String uri) { RDFNode valueAsRdfNode = getOuterEnvelopeGraph().createResource(uri); addMessageProperty(property, valueAsRdfNode); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, URI value) { addMessageProperty(property, value.toString()); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, long value) { addMessageProperty(property, getOuterEnvelopeGraph().createTypedLiteral(value)); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, int value) { addMessageProperty(property, getOuterEnvelopeGraph().createTypedLiteral(value)); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, double value) { addMessageProperty(property, getOuterEnvelopeGraph().createTypedLiteral(value)); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, float value) { addMessageProperty(property, getOuterEnvelopeGraph().createTypedLiteral(value)); } /** * Adds a property to the message resource in the outermost envelope. * * @param property * @param value */ public synchronized void addMessageProperty(Property property, boolean value) { addMessageProperty(property, getOuterEnvelopeGraph().createTypedLiteral(value)); } /** * Creates a copy of the message dataset where all traces * of the envelope graph are deleted. * * @return */ public synchronized Dataset getMessageContent() { if (this.messageContent != null) { return RdfUtils.cloneDataset(this.messageContent); } else { Dataset newMsgContent = DatasetFactory.createGeneral(); Iterator<String> modelNames = this.completeDataset.listNames(); List<String> envelopeGraphNames = getEnvelopeGraphURIs(); //add all models that are not envelope graphs to the messageContent while (modelNames.hasNext()) { String modelName = modelNames.next(); if (envelopeGraphNames.contains(modelName)) { continue; } newMsgContent.addNamedModel(modelName, this.completeDataset.getNamedModel(modelName)); } //copy the default model, but delete the triples referencing the envelope graphs //TODO: this deletion is no longer necessary, right? Model newDefaultModel = ModelFactory.createDefaultModel(); newDefaultModel.add(this.completeDataset.getDefaultModel()); StmtIterator it = newDefaultModel.listStatements(null, RDF.type, WONMSG.ENVELOPE_GRAPH); while (it.hasNext()) { Statement stmt = it.nextStatement(); String subjString = stmt.getSubject().toString(); if (envelopeGraphNames.contains(subjString)) { it.remove(); } } newMsgContent.setDefaultModel(newDefaultModel); this.messageContent = newMsgContent; } return RdfUtils.cloneDataset(this.messageContent); } /** * Returns all content graphs that are attachments, including their signature graphs. * * @return */ public synchronized List<AttachmentHolder> getAttachments() { if (this.attachmentHolders != null) { return this.attachmentHolders; } final List<String> envelopeGraphUris = getEnvelopeGraphURIs(); List<AttachmentHolder> newAttachmentHolders = new ArrayList<>(); String queryString = "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" + "prefix xsd: <http://www.w3.org/2001/XMLSchema#>\n" + "prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" + "prefix won: <http://purl.org/webofneeds/model#>\n" + "prefix msg: <http://purl.org/webofneeds/message#>\n" + "prefix sig: <http://icp.it-risk.iwvi.uni-koblenz.de/ontologies/signature.owl#>\n" + "\n" + "select ?attachmentSigGraphUri ?attachmentGraphUri ?envelopeGraphUri ?attachmentDestinationUri where { \n" + "graph ?attachmentSigGraphUri {?attachmentSigGraphUri " + " a sig:Signature; \n" + " msg:hasSignedGraph ?attachmentGraphUri.\n" + "}\n" + "graph ?envelopeGraphUri {?envelopeGraphUri rdf:type msg:EnvelopeGraph. \n" + " ?messageUri msg:hasAttachment ?attachmentData. \n" + "?attachmentData msg:hasDestinationUri ?attachmentDestinationUri; \n" + " msg:hasAttachmentGraphUri ?attachmentGraphUri.\n" + "}\n" + "}"; Query query = QueryFactory.create(queryString); QuerySolutionMap initialBinding = new QuerySolutionMap(); initialBinding.add("messageUri", new ResourceImpl(getMessageURI().toString())); try (QueryExecution queryExecution = QueryExecutionFactory.create(query, completeDataset)) { queryExecution.getContext().set(TDB.symUnionDefaultGraph, true); ResultSet result = queryExecution.execSelect(); while (result.hasNext()) { QuerySolution solution = result.nextSolution(); String envelopeGraphUri = solution.getResource("envelopeGraphUri").getURI(); if (!envelopeGraphUris.contains(envelopeGraphUri)) { logger.warn("found resource {} of type msg:EnvelopeGraph that is not the URI of an envelope graph in message {}", envelopeGraphUri, this.messageURI); continue; } String sigGraphUri = solution.getResource("attachmentSigGraphUri").getURI().toString(); String attachmentGraphUri = solution.getResource("attachmentGraphUri").getURI(); String attachmentSigGraphUri = solution.getResource("attachmentSigGraphUri").getURI(); String attachmentDestinationUri = solution.getResource("attachmentDestinationUri").getURI(); Dataset attachmentDataset = DatasetFactory.createGeneral(); attachmentDataset.addNamedModel(attachmentGraphUri, this.completeDataset.getNamedModel(attachmentGraphUri)); attachmentDataset.addNamedModel(attachmentSigGraphUri, this.completeDataset.getNamedModel(attachmentSigGraphUri)); AttachmentHolder attachmentHolder = new AttachmentHolder(URI.create(attachmentDestinationUri), attachmentDataset); newAttachmentHolders.add(attachmentHolder); } } catch (Exception e) { throw e; } this.attachmentHolders = newAttachmentHolders; return newAttachmentHolders; } private synchronized Model getOuterEnvelopeGraph() { if (this.outerEnvelopeGraph != null) { return this.outerEnvelopeGraph; } this.outerEnvelopeGraph = completeDataset.getNamedModel(getOuterEnvelopeGraphURI().toString()); return this.outerEnvelopeGraph; } public synchronized URI getOuterEnvelopeGraphURI() { if (this.outerEnvelopeGraphURI != null) { return this.outerEnvelopeGraphURI; } getEnvelopeGraphs(); //also sets the outerEnvelopeUri return this.outerEnvelopeGraphURI; } /** * Returns all envelope graphs found in the message. * <p> * Not that this method has side effects: all intermediate results are cached for re-use. This * concerns the envelopeGraphNames, contentGraphNames and messageURI members. * * @return */ public synchronized List<Model> getEnvelopeGraphs() { //return cached instance if we have it if (this.envelopeGraphs != null) return this.envelopeGraphs; //initialize List<Model> allEnvelopes = new ArrayList<Model>(); this.envelopeGraphNames = new ArrayList<String>(); this.contentGraphNames = new ArrayList<String>(); URI currentMessageURI = null; this.outerEnvelopeGraph = null; Set<String> envelopesContainedInOthers = new HashSet<String>(); Set<String> allEnvelopeGraphNames = new HashSet<String>(); //iterate over named graphs Iterator<String> modelUriIterator = this.completeDataset.listNames(); while (modelUriIterator.hasNext()) { String envelopeGraphUri = modelUriIterator.next(); Model envelopeGraph = this.completeDataset.getNamedModel(envelopeGraphUri); //check if the current named graph is an envelope graph (G rdf:type wonmsg:EnvelopeGraph) if (isEnvelopeGraph(envelopeGraphUri, envelopeGraph)) { this.envelopeGraphNames.add(envelopeGraphUri); allEnvelopeGraphNames.add(envelopeGraphUri); allEnvelopes.add(envelopeGraph); currentMessageURI = findMessageUri(envelopeGraph, envelopeGraphUri); //check if the envelope contains references to 'contained' envelopes and remember their names List<String> containedEnvelopes = findContainedEnvelopeUris(envelopeGraph, envelopeGraphUri); envelopesContainedInOthers.addAll(containedEnvelopes); if (currentMessageURI != null) { for (NodeIterator it = getContentGraphReferences(envelopeGraph, envelopeGraph.getResource(currentMessageURI.toString())); it .hasNext(); ) { RDFNode node = it.next(); this.contentGraphNames.add(node.asResource().toString()); } } } } Set<String> candidatesForOuterEnvelope = Sets.symmetricDifference(allEnvelopeGraphNames, envelopesContainedInOthers); //we've now visited all named graphs. We should now have exactly one candidate for the outer envelope if (candidatesForOuterEnvelope.size() != 1) { throw new IllegalStateException(String.format("Message dataset must contain exactly one envelope graph that is " + "not included " + "in another one, but found %d", candidatesForOuterEnvelope.size())); } String outerEnvelopeUri = candidatesForOuterEnvelope.iterator().next(); this.outerEnvelopeGraphURI = URI.create(outerEnvelopeUri); this.outerEnvelopeGraph = this.completeDataset.getNamedModel(outerEnvelopeUri); this.envelopeGraphs = allEnvelopes; return Collections.unmodifiableList(allEnvelopes.stream().map(m -> RdfUtils.cloneModel(m)).collect(Collectors.toList())); } private synchronized URI findMessageUri(final Model model, final String modelUri) { RDFNode messageUriNode = RdfUtils.findOnePropertyFromResource(model, model.getResource(modelUri), RDFG.SUBGRAPH_OF); return URI.create(messageUriNode.asResource().getURI()); } private synchronized List<String> findContainedEnvelopeUris(final Model envelopeGraph, final String envelopeGraphUri) { Resource envelopeGraphResource = envelopeGraph.getResource(envelopeGraphUri); StmtIterator it = envelopeGraphResource.listProperties(WONMSG.CONTAINS_ENVELOPE); if (it.hasNext()) { List ret = new ArrayList<String>(); while (it.hasNext()) { ret.add(it.nextStatement().getObject().asResource().getURI()); } return ret; } return Collections.emptyList(); } public boolean isEnvelopeGraph(final String modelUri, final Model model) { return model.contains(model.getResource(modelUri), RDF.type, WONMSG.ENVELOPE_GRAPH); } public synchronized List<String> getEnvelopeGraphURIs() { if (this.envelopeGraphs == null) { getEnvelopeGraphs(); //also sets envelopeGraphNames } return Collections.unmodifiableList(this.envelopeGraphNames); } public synchronized List<String> getContentGraphURIs() { //since there may not be any content graphs, we can't check //if this.contentGraphNames == null. We instead have to check //if we ran the detection of the envelope graphs at least once. if (this.envelopeGraphs == null) { getEnvelopeGraphs(); //also sets envelopeGraphNames } return Collections.unmodifiableList(this.contentGraphNames); } private synchronized NodeIterator getContentGraphReferences(Model model, Resource envelopeGraphResource) { return model.listObjectsOfProperty(envelopeGraphResource, WONMSG.HAS_CONTENT_PROPERTY); } public synchronized URI getMessageURI() { if (this.messageURI == null) { this.messageURI = findMessageUri(getOuterEnvelopeGraph(), getOuterEnvelopeGraphURI().toString()); } return this.messageURI; } public synchronized WonMessageType getMessageType() { if (this.messageType == null) { URI type = getEnvelopePropertyURIValue(WONMSG.HAS_MESSAGE_TYPE_PROPERTY); this.messageType = WonMessageType.getWonMessageType(type); } return this.messageType; } public synchronized WonMessageDirection getEnvelopeType() { if (this.envelopeType == null) { URI type = getEnvelopePropertyURIValue(RDF.type); if (type != null) { this.envelopeType = WonMessageDirection.getWonMessageDirection(type); } } return this.envelopeType; } public synchronized URI getSenderURI() { if (this.senderURI == null) { this.senderURI = getEnvelopePropertyURIValue(WONMSG.SENDER_PROPERTY); } return this.senderURI; } public synchronized URI getSenderNeedURI() { if (this.senderNeedURI == null) { this.senderNeedURI = getEnvelopePropertyURIValue(WONMSG.SENDER_NEED_PROPERTY); } return this.senderNeedURI; } public synchronized URI getSenderNodeURI() { if (this.senderNodeURI == null) { this.senderNodeURI = getEnvelopePropertyURIValue(WONMSG.SENDER_NODE_PROPERTY); } return this.senderNodeURI; } public synchronized URI getReceiverURI() { if (this.receiverURI == null) { this.receiverURI = getEnvelopePropertyURIValue(WONMSG.RECEIVER_PROPERTY); } return this.receiverURI; } public synchronized URI getReceiverNeedURI() { if (this.receiverNeedURI == null) { this.receiverNeedURI = getEnvelopePropertyURIValue(WONMSG.RECEIVER_NEED_PROPERTY); } return this.receiverNeedURI; } public synchronized URI getReceiverNodeURI() { if (this.receiverNodeURI == null) { this.receiverNodeURI = getEnvelopePropertyURIValue(WONMSG.RECEIVER_NODE_PROPERTY); } return this.receiverNodeURI; } public synchronized List<URI> getRefersTo() { if (this.refersTo == null) { this.refersTo = getEnvelopePropertyURIValues(WONMSG.REFERS_TO_PROPERTY); } return this.refersTo; } public synchronized URI getIsResponseToMessageURI() { if (this.isResponseToMessageURI == null) { this.isResponseToMessageURI = getEnvelopePropertyURIValue(WONMSG.IS_RESPONSE_TO); } return this.isResponseToMessageURI; } public synchronized URI getIsRemoteResponseToMessageURI() { if (this.isRemoteResponseToMessageURI == null) { this.isRemoteResponseToMessageURI = getEnvelopePropertyURIValue(WONMSG.IS_REMOTE_RESPONSE_TO); } return this.isRemoteResponseToMessageURI; } public synchronized URI getCorrespondingRemoteMessageURI() { if (this.correspondingRemoteMessageURI == null) { this.correspondingRemoteMessageURI = getEnvelopePropertyURIValue(WONMSG.HAS_CORRESPONDING_REMOTE_MESSAGE); } return this.correspondingRemoteMessageURI; } public synchronized WonMessageType getIsResponseToMessageType() { if (this.isResponseToMessageType == null) { URI typeURI = getEnvelopePropertyURIValue(WONMSG.IS_RESPONSE_TO_MESSAGE_TYPE); if (typeURI != null) { this.isResponseToMessageType = WonMessageType.getWonMessageType(typeURI); } } return isResponseToMessageType; } public synchronized URI getEnvelopePropertyURIValue(URI propertyURI) { Property property = this.completeDataset.getDefaultModel().createProperty(propertyURI.toString()); return getEnvelopePropertyURIValue(property); } public synchronized URI getEnvelopePropertyURIValue(Property property) { Model currentEnvelope = getOuterEnvelopeGraph(); URI currentEnvelopeUri = getOuterEnvelopeGraphURI(); //TODO would make sense to order envelope graphs in order from container to containee in the first place, //if proper done, we should avoid ending up in infinite loop if someone sends us malformed envelopes that // contain-in-other circular... while (currentEnvelope != null) { URI currentMessageURI = findMessageUri(currentEnvelope, currentEnvelopeUri.toString()); StmtIterator it = currentEnvelope.listStatements(currentEnvelope.getResource(currentMessageURI.toString()), property, (RDFNode) null); if (it.hasNext()) { return URI.create(it.nextStatement().getObject().asResource().toString()); } // move to the next envelope currentEnvelopeUri = RdfUtils.findFirstObjectUri(currentEnvelope, WONMSG.CONTAINS_ENVELOPE, null, true, true); currentEnvelope = null; if (currentEnvelopeUri != null) { currentEnvelope = this.completeDataset.getNamedModel(currentEnvelopeUri.toString()); } } return null; } private synchronized URI getEnvelopeSubjectURIValue(Property property, RDFNode object) { for (Model envelopeGraph : getEnvelopeGraphs()) { URI val = RdfUtils.findFirstSubjectUri(envelopeGraph, property, object, true, true); if (val != null) { return val; } } return null; } private synchronized List<URI> getEnvelopePropertyURIValues(Property property) { List<URI> values = new ArrayList<URI>(); Model currentEnvelope = getOuterEnvelopeGraph(); URI currentEnvelopeUri = getOuterEnvelopeGraphURI(); //TODO would make sense to order envelope graphs in order from container to containee in the first place while (currentEnvelope != null) { URI currentMessageURI = findMessageUri(currentEnvelope, currentEnvelopeUri.toString()); StmtIterator it = currentEnvelope.listStatements(currentEnvelope.getResource(currentMessageURI.toString()), property, (RDFNode) null); while (it.hasNext()) { values.add(URI.create(it.nextStatement().getObject().asResource().toString())); } currentEnvelopeUri = RdfUtils.findFirstObjectUri(currentEnvelope, WONMSG.CONTAINS_ENVELOPE, null, true, true); currentEnvelope = null; if (currentEnvelopeUri != null) { currentEnvelope = this.completeDataset.getNamedModel(currentEnvelopeUri.toString()); } } return values; } //Used to remember attachment graph uri and destination uri during the process of extracting attachments. public class AttachmentMetaData { URI attachmentGraphUri; URI destinationUri; AttachmentMetaData(URI attachmentGraphUri, URI destinationUri) { this.attachmentGraphUri = attachmentGraphUri; this.destinationUri = destinationUri; } public URI getAttachmentGraphUri() { return attachmentGraphUri; } public URI getDestinationUri() { return destinationUri; } } public static class AttachmentHolder { private URI destinationUri; //holds the attachment graph and the signature graph private Dataset attachmentDataset; public AttachmentHolder(URI destinationUri, Dataset attachmentDataset) { this.destinationUri = destinationUri; this.attachmentDataset = attachmentDataset; } public URI getDestinationUri() { return destinationUri; } public Dataset getAttachmentDataset() { return attachmentDataset; } } }