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.integration; 019 020import static java.lang.Integer.parseInt; 021import static java.util.Arrays.asList; 022import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel; 023import static org.apache.jena.vocabulary.RDF.type; 024import static org.fcrepo.camel.FcrepoHeaders.FCREPO_AGENT; 025import static org.fcrepo.camel.FcrepoHeaders.FCREPO_DATE_TIME; 026import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE; 027import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_ID; 028import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI; 029import static org.slf4j.LoggerFactory.getLogger; 030 031import java.util.Map; 032import java.util.HashMap; 033import java.io.IOException; 034 035import org.apache.camel.Produce; 036import org.apache.camel.Exchange; 037import org.apache.camel.EndpointInject; 038import org.apache.camel.ProducerTemplate; 039import org.apache.camel.builder.RouteBuilder; 040import org.apache.camel.builder.xml.XPathBuilder; 041import org.apache.camel.builder.xml.Namespaces; 042import org.apache.camel.component.mock.MockEndpoint; 043import org.apache.camel.test.junit4.CamelTestSupport; 044import org.apache.jena.fuseki.embedded.FusekiEmbeddedServer; 045import org.apache.jena.query.Dataset; 046import org.apache.jena.sparql.core.DatasetImpl; 047import org.fcrepo.camel.audit.triplestore.AuditHeaders; 048import org.fcrepo.camel.audit.triplestore.AuditSparqlProcessor; 049import org.junit.After; 050import org.junit.Before; 051import org.junit.Test; 052import org.slf4j.Logger; 053 054/** 055 * Represents an integration test for interacting with an external triplestore. 056 * 057 * @author Aaron Coburn 058 * @since Nov 8, 2014 059 */ 060public class AuditSparqlIT extends CamelTestSupport { 061 062 final private Logger logger = getLogger(AuditSparqlIT.class); 063 064 private static final int FUSEKI_PORT = parseInt(System.getProperty( 065 "fuseki.dynamic.test.port", "8080")); 066 067 private static FusekiEmbeddedServer server = null; 068 069 private static final String PREMIS = "http://www.loc.gov/premis/rdf/v1#"; 070 071 private static final String USER = "bypassAdmin"; 072 073 private static final String USER_AGENT = "curl/7.37.1"; 074 075 private static final String EVENT_BASE_URI = "http://example.com/event"; 076 077 private static final String EVENT_ID = "ab/cd/ef/gh/abcdefgh12345678"; 078 079 private static final String EVENT_URI = EVENT_BASE_URI + "/" + EVENT_ID; 080 081 private static final String AS_NS = "https://www.w3.org/ns/activitystreams#"; 082 083 @EndpointInject(uri = "mock:sparql.update") 084 protected MockEndpoint sparqlUpdateEndpoint; 085 086 @EndpointInject(uri = "mock:sparql.query") 087 protected MockEndpoint sparqlQueryEndpoint; 088 089 @Produce(uri = "direct:start") 090 protected ProducerTemplate template; 091 092 @Before 093 public void setup() throws Exception { 094 final Dataset ds = new DatasetImpl(createDefaultModel()); 095 server = FusekiEmbeddedServer.create().setPort(FUSEKI_PORT).setContextPath("/fuseki").add("/test", ds).build(); 096 logger.info("Starting EmbeddedFusekiServer on port {}", FUSEKI_PORT); 097 server.start(); 098 } 099 100 @After 101 public void tearDown() throws Exception { 102 logger.info("Stopping EmbeddedFusekiServer"); 103 server.stop(); 104 } 105 106 private Map<String, Object> getEventHeaders() { 107 // send an audit event to an external triplestore 108 final Map<String, Object> headers = new HashMap<>(); 109 headers.put(FCREPO_URI, "http://localhost/rest/foo"); 110 headers.put(FCREPO_EVENT_TYPE, asList(AS_NS + "Create", AS_NS + "Update")); 111 headers.put(FCREPO_DATE_TIME, "2015-04-10T14:30:36Z"); 112 headers.put(FCREPO_AGENT, asList(USER, USER_AGENT)); 113 headers.put(FCREPO_EVENT_ID, EVENT_ID); 114 115 return headers; 116 } 117 118 @Test 119 public void testAuditEventTypeTriples() throws Exception { 120 sparqlUpdateEndpoint.expectedMessageCount(2); 121 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 122 123 sparqlQueryEndpoint.expectedMessageCount(1); 124 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 125 sparqlQueryEndpoint.expectedBodiesReceivedInAnyOrder( 126 "http://id.loc.gov/vocabulary/preservation/eventType/cre"); 127 128 template.sendBody("direct:clear", null); 129 template.sendBodyAndHeaders(null, getEventHeaders()); 130 131 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 132 "query=SELECT ?o WHERE { <" + EVENT_URI + "> <" + PREMIS + "hasEventType> ?o }"); 133 134 sparqlQueryEndpoint.assertIsSatisfied(); 135 sparqlUpdateEndpoint.assertIsSatisfied(); 136 } 137 138 @Test 139 public void testAuditEventRelatedTriples() throws Exception { 140 sparqlUpdateEndpoint.expectedMessageCount(2); 141 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 142 143 sparqlQueryEndpoint.expectedMessageCount(2); 144 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 145 sparqlQueryEndpoint.expectedBodiesReceivedInAnyOrder( 146 "http://localhost/rest/foo" 147 ); 148 149 template.sendBody("direct:clear", null); 150 template.sendBodyAndHeaders(null, getEventHeaders()); 151 152 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 153 "query=SELECT ?o WHERE { <" + EVENT_URI + "> <" + PREMIS + "hasEventRelatedObject> ?o }"); 154 155 sparqlQueryEndpoint.assertIsSatisfied(); 156 sparqlUpdateEndpoint.assertIsSatisfied(); 157 } 158 159 @Test 160 public void testAuditEventDateTriples() throws Exception { 161 sparqlUpdateEndpoint.expectedMessageCount(2); 162 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 163 164 sparqlQueryEndpoint.expectedMessageCount(2); 165 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 166 sparqlQueryEndpoint.expectedBodiesReceivedInAnyOrder( 167 "2015-04-10T14:30:36Z" 168 ); 169 170 template.sendBody("direct:clear", null); 171 template.sendBodyAndHeaders(null, getEventHeaders()); 172 173 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 174 "query=SELECT ?o WHERE { <" + EVENT_URI + "> <" + PREMIS + "hasEventDateTime> ?o }"); 175 176 sparqlQueryEndpoint.assertIsSatisfied(); 177 sparqlUpdateEndpoint.assertIsSatisfied(); 178 } 179 180 @Test 181 public void testAuditEventAgentTriples() throws Exception { 182 sparqlUpdateEndpoint.expectedMessageCount(2); 183 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 184 185 sparqlQueryEndpoint.expectedMessageCount(2); 186 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 187 sparqlQueryEndpoint.expectedBodiesReceivedInAnyOrder( 188 USER, 189 USER_AGENT 190 ); 191 192 template.sendBody("direct:clear", null); 193 template.sendBodyAndHeaders(null, getEventHeaders()); 194 195 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 196 "query=SELECT ?o WHERE { <" + EVENT_URI + "> <" + PREMIS + "hasEventRelatedAgent> ?o }"); 197 198 sparqlQueryEndpoint.assertIsSatisfied(); 199 sparqlUpdateEndpoint.assertIsSatisfied(); 200 } 201 202 @Test 203 public void testAuditEventAllTriples() throws Exception { 204 sparqlUpdateEndpoint.expectedMessageCount(2); 205 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 206 207 sparqlQueryEndpoint.expectedMessageCount(8); 208 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 209 210 template.sendBody("direct:clear", null); 211 template.sendBodyAndHeaders(null, getEventHeaders()); 212 213 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 214 "query=SELECT ?o WHERE { <" + EVENT_URI + "> ?p ?o }"); 215 216 sparqlQueryEndpoint.assertIsSatisfied(); 217 sparqlUpdateEndpoint.assertIsSatisfied(); 218 } 219 220 @Test 221 public void testAuditTypeTriples() throws Exception { 222 sparqlUpdateEndpoint.expectedMessageCount(2); 223 sparqlUpdateEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 224 225 sparqlQueryEndpoint.expectedMessageCount(2); 226 sparqlQueryEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 200); 227 sparqlQueryEndpoint.expectedBodiesReceivedInAnyOrder( 228 "http://www.loc.gov/premis/rdf/v1#Event", 229 "http://fedora.info/definitions/v4/audit#InternalEvent", 230 "http://www.w3.org/ns/prov#InstantaneousEvent" 231 ); 232 233 template.sendBody("direct:clear", null); 234 template.sendBodyAndHeaders(null, getEventHeaders()); 235 236 template.sendBodyAndHeader("direct:query", null, Exchange.HTTP_QUERY, 237 "query=SELECT ?o WHERE { <" + EVENT_URI + "> <" + type.toString() + "> ?o }"); 238 239 sparqlQueryEndpoint.assertIsSatisfied(); 240 sparqlUpdateEndpoint.assertIsSatisfied(); 241 } 242 243 @Override 244 protected RouteBuilder createRouteBuilder() throws Exception { 245 246 final Namespaces ns = new Namespaces("sparql", "http://www.w3.org/2005/sparql-results#"); 247 248 final XPathBuilder xpath = new XPathBuilder( 249 "//sparql:result/sparql:binding[@name='o']"); 250 xpath.namespaces(ns); 251 252 final XPathBuilder uriResult = new XPathBuilder( 253 "/sparql:binding/sparql:uri"); 254 uriResult.namespaces(ns); 255 256 final XPathBuilder literalResult = new XPathBuilder( 257 "/sparql:binding/sparql:literal"); 258 literalResult.namespaces(ns); 259 260 return new RouteBuilder() { 261 public void configure() throws IOException { 262 final String fuseki_url = "http4://localhost:" + Integer.toString(FUSEKI_PORT); 263 264 from("direct:start") 265 .setHeader(AuditHeaders.EVENT_BASE_URI, constant(EVENT_BASE_URI)) 266 .process(new AuditSparqlProcessor()) 267 .to(fuseki_url + "/fuseki/test/update") 268 .to("mock:sparql.update"); 269 270 from("direct:query") 271 .to(fuseki_url + "/fuseki/test/query") 272 .split(xpath) 273 .choice() 274 .when().xpath("/sparql:binding/sparql:uri", String.class, ns) 275 .transform().xpath("/sparql:binding/sparql:uri/text()", String.class, ns) 276 .to("mock:sparql.query") 277 .when().xpath("/sparql:binding/sparql:literal", String.class, ns) 278 .transform().xpath("/sparql:binding/sparql:literal/text()", String.class, ns) 279 .to("mock:sparql.query"); 280 281 from("direct:clear") 282 .transform().constant("update=DELETE WHERE { ?s ?o ?p }") 283 .setHeader(Exchange.CONTENT_TYPE).constant("application/x-www-form-urlencoded") 284 .setHeader(Exchange.HTTP_METHOD).constant("POST") 285 .to(fuseki_url + "/fuseki/test/update") 286 .to("mock:sparql.update"); 287 288 } 289 }; 290 } 291}