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