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 org.fcrepo.camel.RdfNamespaces.RDF; 021import static org.fcrepo.camel.RdfNamespaces.REPOSITORY; 022import static com.hp.hpl.jena.datatypes.xsd.XSDDatatype.XSDdateTime; 023import static com.hp.hpl.jena.datatypes.xsd.XSDDatatype.XSDstring; 024import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel; 025import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty; 026import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource; 027import static com.hp.hpl.jena.rdf.model.ResourceFactory.createTypedLiteral; 028 029import java.io.ByteArrayOutputStream; 030import java.io.IOException; 031import java.text.DateFormat; 032import java.text.SimpleDateFormat; 033import java.util.Date; 034import java.util.TimeZone; 035 036import org.fcrepo.camel.JmsHeaders; 037import org.fcrepo.camel.processor.ProcessorUtils; 038 039import org.apache.camel.Exchange; 040import org.apache.camel.Message; 041import org.apache.camel.Processor; 042import com.hp.hpl.jena.rdf.model.Model; 043import com.hp.hpl.jena.rdf.model.Property; 044import com.hp.hpl.jena.rdf.model.Resource; 045 046/** 047 * A processor that converts an audit message into a sparql-update 048 * statement for an external triplestore. 049 * 050 * @author Aaron Coburn 051 * @author escowles 052 * @since 2015-04-09 053 */ 054 055public class AuditSparqlProcessor implements Processor { 056 057 static final String AUDIT = "http://fedora.info/definitions/v4/audit#"; 058 static final String PREMIS = "http://www.loc.gov/premis/rdf/v1#"; 059 static final String PROV = "http://www.w3.org/ns/prov#"; 060 static final String XSD = "http://www.w3.org/2001/XMLSchema#"; 061 static final String EVENT_TYPE = "http://id.loc.gov/vocabulary/preservation/eventType/"; 062 static final String EVENT_NAMESPACE = "http://fedora.info/definitions/v4/event#"; 063 064 static final String CONTENT_MOD = AUDIT + "contentModification"; 065 static final String CONTENT_REM = AUDIT + "contentRemoval"; 066 static final String METADATA_MOD = AUDIT + "metadataModification"; 067 068 static final String CONTENT_ADD = EVENT_TYPE + "ing"; 069 static final String OBJECT_ADD = EVENT_TYPE + "cre"; 070 static final String OBJECT_REM = EVENT_TYPE + "del"; 071 072 static final String HAS_CONTENT = REPOSITORY + "hasContent"; 073 074 static final String NODE_ADDED = REPOSITORY + "NODE_ADDED"; 075 static final String NODE_REMOVED = REPOSITORY + "NODE_REMOVED"; 076 static final String PROPERTY_CHANGED = REPOSITORY + "PROPERTY_CHANGED"; 077 078 /** 079 * Define how a message should be processed. 080 * 081 * @param exchange the current camel message exchange 082 */ 083 public void process(final Exchange exchange) throws Exception { 084 final Message in = exchange.getIn(); 085 final String eventURIBase = in.getHeader(AuditHeaders.EVENT_BASE_URI, String.class); 086 final String eventID = in.getHeader(JmsHeaders.EVENT_ID, String.class); 087 final Resource eventURI = createResource(eventURIBase + "/" + eventID); 088 089 // generate SPARQL Update 090 final StringBuilder query = new StringBuilder("update="); 091 query.append(ProcessorUtils.insertData(serializedGraphForMessage(in, eventURI), null)); 092 093 // update exchange 094 in.setBody(query.toString()); 095 in.setHeader(AuditHeaders.EVENT_URI, eventURI.toString()); 096 in.setHeader(Exchange.CONTENT_TYPE, "application/x-www-form-urlencoded"); 097 in.setHeader(Exchange.HTTP_METHOD, "POST"); 098 } 099 100 // namespaces and properties 101 private static final Resource INTERNAL_EVENT = createResource(AUDIT + "InternalEvent"); 102 private static final Resource PREMIS_EVENT = createResource(PREMIS + "Event"); 103 private static final Resource PROV_EVENT = createResource(PROV + "InstantaneousEvent"); 104 105 private static final Property PREMIS_TIME = createProperty(PREMIS + "hasEventDateTime"); 106 private static final Property PREMIS_OBJ = createProperty(PREMIS + "hasEventRelatedObject"); 107 private static final Property PREMIS_AGENT = createProperty(PREMIS + "hasEventRelatedAgent"); 108 private static final Property PREMIS_TYPE = createProperty(PREMIS + "hasEventType"); 109 private static final Property RDF_TYPE = createProperty(RDF + "type"); 110 111 private static final String EMPTY_STRING = ""; 112 private static final String RESOURCE_TYPES = "org.fcrepo.jms.resourceType"; 113 114 /** 115 * Convert a Camel message to audit event description. 116 * @param message Camel message produced by an audit event 117 * @param subject RDF subject of the audit description 118 */ 119 private static String serializedGraphForMessage(final Message message, final Resource subject) throws IOException { 120 121 // serialize triples 122 final ByteArrayOutputStream serializedGraph = new ByteArrayOutputStream(); 123 final Model model = createDefaultModel(); 124 125 // get info from jms message headers 126 final String eventType = message.getHeader(JmsHeaders.EVENT_TYPE, EMPTY_STRING, String.class); 127 final Long timestamp = message.getHeader(JmsHeaders.TIMESTAMP, 0, Long.class); 128 final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 129 df.setTimeZone(TimeZone.getTimeZone("UTC")); 130 final String date = df.format(new Date(timestamp)); 131 final String user = message.getHeader(JmsHeaders.USER, EMPTY_STRING, String.class); 132 final String agent = message.getHeader(JmsHeaders.USER_AGENT, EMPTY_STRING, String.class); 133 final String properties = message.getHeader(JmsHeaders.PROPERTIES, EMPTY_STRING, String.class); 134 final String resourceTypes = message.getHeader(RESOURCE_TYPES, EMPTY_STRING, String.class); 135 final String identifier = ProcessorUtils.getSubjectUri(message); 136 final String premisType = getAuditEventType(eventType, properties, resourceTypes); 137 138 model.add( model.createStatement(subject, RDF_TYPE, INTERNAL_EVENT) ); 139 model.add( model.createStatement(subject, RDF_TYPE, PREMIS_EVENT) ); 140 model.add( model.createStatement(subject, RDF_TYPE, PROV_EVENT) ); 141 142 // basic event info 143 model.add( model.createStatement(subject, PREMIS_TIME, createTypedLiteral(date, XSDdateTime)) ); 144 model.add( model.createStatement(subject, PREMIS_OBJ, createResource(identifier)) ); 145 model.add( model.createStatement(subject, PREMIS_AGENT, createTypedLiteral(user, XSDstring)) ); 146 model.add( model.createStatement(subject, PREMIS_AGENT, createTypedLiteral(agent, XSDstring)) ); 147 if (premisType != null) { 148 model.add(model.createStatement(subject, PREMIS_TYPE, createResource(premisType))); 149 } 150 151 model.write(serializedGraph, "N-TRIPLE"); 152 return serializedGraph.toString("UTF-8"); 153 } 154 155 /** 156 * Returns the Audit event type based on fedora event type and properties. 157 * 158 * @param eventType from Fedora 159 * @param properties associated with the Fedora event 160 * @return Audit event 161 */ 162 private static String getAuditEventType(final String eventType, final String properties, 163 final String resourceType) { 164 // mapping event type/properties to audit event type 165 if (eventType.contains(NODE_ADDED) || eventType.contains(EVENT_NAMESPACE + "ResourceCreation")) { 166 if (properties != null && properties.contains(HAS_CONTENT)) { 167 return CONTENT_ADD; 168 } else if (resourceType != null && resourceType.contains(REPOSITORY + "Binary")) { 169 return CONTENT_ADD; 170 } else { 171 return OBJECT_ADD; 172 } 173 } else if (eventType.contains(NODE_REMOVED) || eventType.contains(EVENT_NAMESPACE + "ResourceDeletion")) { 174 if (properties != null && properties.contains(HAS_CONTENT)) { 175 return CONTENT_REM; 176 } else if (resourceType != null && resourceType.contains(REPOSITORY + "Binary")) { 177 return CONTENT_REM; 178 } else { 179 return OBJECT_REM; 180 } 181 } else if (eventType.contains(PROPERTY_CHANGED) || 182 eventType.contains(EVENT_NAMESPACE + "ResourceModification")) { 183 if (properties != null && properties.contains(HAS_CONTENT)) { 184 return CONTENT_MOD; 185 } else if (resourceType != null && resourceType.contains(REPOSITORY + "Binary")) { 186 return CONTENT_MOD; 187 } else { 188 return METADATA_MOD; 189 } 190 } 191 return null; 192 } 193 194 195}