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