001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.camel.audit.triplestore; 007 008import static java.util.Collections.emptyList; 009import static java.util.Optional.empty; 010import static java.util.Optional.of; 011import static org.apache.jena.riot.RDFDataMgr.write; 012import static org.apache.jena.riot.RDFFormat.NTRIPLES; 013import static org.apache.jena.vocabulary.RDF.type; 014import static org.fcrepo.camel.FcrepoHeaders.FCREPO_AGENT; 015import static org.fcrepo.camel.FcrepoHeaders.FCREPO_DATE_TIME; 016import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_ID; 017import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE; 018import static org.fcrepo.camel.FcrepoHeaders.FCREPO_RESOURCE_TYPE; 019import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI; 020import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDdateTime; 021import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring; 022import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel; 023import static org.apache.jena.rdf.model.ResourceFactory.createProperty; 024import static org.apache.jena.rdf.model.ResourceFactory.createResource; 025import static org.apache.jena.rdf.model.ResourceFactory.createTypedLiteral; 026 027import java.io.ByteArrayOutputStream; 028import java.io.IOException; 029import java.util.List; 030import java.util.Optional; 031 032import org.fcrepo.camel.processor.ProcessorUtils; 033 034import org.apache.camel.Exchange; 035import org.apache.camel.Message; 036import org.apache.camel.Processor; 037import org.apache.jena.rdf.model.Model; 038import org.apache.jena.rdf.model.Property; 039import org.apache.jena.rdf.model.Resource; 040 041/** 042 * A processor that converts an audit message into a sparql-update 043 * statement for an external triplestore. 044 * 045 * @author Aaron Coburn 046 * @author escowles 047 * @since 2015-04-09 048 */ 049 050public class AuditSparqlProcessor implements Processor { 051 052 static final String AUDIT = "http://fedora.info/definitions/v4/audit#"; 053 static final String PREMIS = "http://www.loc.gov/premis/rdf/v1#"; 054 static final String PROV = "http://www.w3.org/ns/prov#"; 055 static final String XSD = "http://www.w3.org/2001/XMLSchema#"; 056 static final String EVENT_TYPE = "http://id.loc.gov/vocabulary/preservation/eventType/"; 057 static final String EVENT_NAMESPACE = "http://fedora.info/definitions/v4/event#"; 058 static final String AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#"; 059 static final String REPOSITORY = "http://fedora.info/definitions/v4/repository#"; 060 061 static final String CONTENT_MOD = AUDIT + "contentModification"; 062 static final String CONTENT_REM = AUDIT + "contentRemoval"; 063 static final String METADATA_MOD = AUDIT + "metadataModification"; 064 065 static final String CONTENT_ADD = EVENT_TYPE + "ing"; 066 static final String OBJECT_ADD = EVENT_TYPE + "cre"; 067 static final String OBJECT_REM = EVENT_TYPE + "del"; 068 069 /** 070 * Define how a message should be processed. 071 * 072 * @param exchange the current camel message exchange 073 */ 074 public void process(final Exchange exchange) throws Exception { 075 final Message in = exchange.getIn(); 076 final String eventURIBase = in.getHeader(AuditHeaders.EVENT_BASE_URI, String.class); 077 final String eventID = in.getHeader(FCREPO_EVENT_ID, String.class); 078 final Resource eventURI = createResource(eventURIBase + "/" + eventID); 079 080 // generate SPARQL Update 081 final StringBuilder query = new StringBuilder("update="); 082 query.append(ProcessorUtils.insertData(serializedGraphForMessage(in, eventURI), "")); 083 084 // update exchange 085 in.setBody(query.toString()); 086 in.setHeader(AuditHeaders.EVENT_URI, eventURI.toString()); 087 in.setHeader(Exchange.CONTENT_TYPE, "application/x-www-form-urlencoded"); 088 in.setHeader(Exchange.HTTP_METHOD, "POST"); 089 } 090 091 // namespaces and properties 092 private static final Resource INTERNAL_EVENT = createResource(AUDIT + "InternalEvent"); 093 private static final Resource PREMIS_EVENT = createResource(PREMIS + "Event"); 094 private static final Resource PROV_EVENT = createResource(PROV + "InstantaneousEvent"); 095 096 private static final Property PREMIS_TIME = createProperty(PREMIS + "hasEventDateTime"); 097 private static final Property PREMIS_OBJ = createProperty(PREMIS + "hasEventRelatedObject"); 098 private static final Property PREMIS_AGENT = createProperty(PREMIS + "hasEventRelatedAgent"); 099 private static final Property PREMIS_TYPE = createProperty(PREMIS + "hasEventType"); 100 101 private static final String EMPTY_STRING = ""; 102 103 /** 104 * Convert a Camel message to audit event description. 105 * @param message Camel message produced by an audit event 106 * @param subject RDF subject of the audit description 107 */ 108 private static String serializedGraphForMessage(final Message message, final Resource subject) throws IOException { 109 110 // serialize triples 111 final ByteArrayOutputStream serializedGraph = new ByteArrayOutputStream(); 112 final Model model = createDefaultModel(); 113 114 // get info from jms message headers 115 @SuppressWarnings("unchecked") 116 final List<String> eventType = message.getHeader(FCREPO_EVENT_TYPE, emptyList(), List.class); 117 final String dateTime = message.getHeader(FCREPO_DATE_TIME, EMPTY_STRING, String.class); 118 @SuppressWarnings("unchecked") 119 final List<String> agents = message.getHeader(FCREPO_AGENT, emptyList(), List.class); 120 @SuppressWarnings("unchecked") 121 final List<String> resourceTypes = message.getHeader(FCREPO_RESOURCE_TYPE, emptyList(), List.class); 122 final String identifier = message.getHeader(FCREPO_URI, EMPTY_STRING, String.class); 123 final Optional<String> premisType = getAuditEventType(eventType, resourceTypes); 124 125 model.add( model.createStatement(subject, type, INTERNAL_EVENT) ); 126 model.add( model.createStatement(subject, type, PREMIS_EVENT) ); 127 model.add( model.createStatement(subject, type, PROV_EVENT) ); 128 129 // basic event info 130 model.add( model.createStatement(subject, PREMIS_TIME, createTypedLiteral(dateTime, XSDdateTime)) ); 131 model.add( model.createStatement(subject, PREMIS_OBJ, createResource(identifier)) ); 132 133 agents.forEach(agent -> { 134 model.add( model.createStatement(subject, PREMIS_AGENT, createTypedLiteral(agent, XSDstring)) ); 135 }); 136 137 premisType.ifPresent(rdfType -> { 138 model.add(model.createStatement(subject, PREMIS_TYPE, createResource(rdfType))); 139 }); 140 141 write(serializedGraph, model, NTRIPLES); 142 return serializedGraph.toString("UTF-8"); 143 } 144 145 /** 146 * Returns the Audit event type based on fedora event type and properties. 147 * 148 * @param eventType from Fedora 149 * @param resourceType associated with the Fedora event 150 * @return Audit event 151 */ 152 private static Optional<String> getAuditEventType(final List<String> eventType, final List<String> resourceType) { 153 // mapping event type/properties to audit event type 154 if (eventType.contains(EVENT_NAMESPACE + "ResourceCreation") || eventType.contains(AS_NAMESPACE + "Create")) { 155 if (resourceType.contains(REPOSITORY + "Binary")) { 156 return of(CONTENT_ADD); 157 } else { 158 return of(OBJECT_ADD); 159 } 160 } else if (eventType.contains(EVENT_NAMESPACE + "ResourceDeletion") || 161 eventType.contains(AS_NAMESPACE + "Delete")) { 162 if (resourceType.contains(REPOSITORY + "Binary")) { 163 return of(CONTENT_REM); 164 } else { 165 return of(OBJECT_REM); 166 } 167 } else if (eventType.contains(EVENT_NAMESPACE + "ResourceModification") || 168 eventType.contains(AS_NAMESPACE + "Update")) { 169 if (resourceType.contains(REPOSITORY + "Binary")) { 170 return of(CONTENT_MOD); 171 } else { 172 return of(METADATA_MOD); 173 } 174 } 175 return empty(); 176 } 177 178 179}