package edu.kit.aifb.cumulus.webapp; import static edu.kit.aifb.cumulus.WebTestUtils.clean; import static edu.kit.aifb.cumulus.WebTestUtils.newTripleStore; import static edu.kit.aifb.cumulus.WebTestUtils.tmpFile; import static edu.kit.aifb.cumulus.util.Util.parseAsList; import static edu.kit.aifb.cumulus.util.Util.parseNX; import static edu.kit.aifb.cumulus.webapp.writer.HTMLWriter.HTML_FORMAT; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.openrdf.model.Model; import org.openrdf.model.Statement; import org.openrdf.model.Value; import org.openrdf.model.impl.LinkedHashModel; import org.openrdf.model.impl.ValueFactoryImpl; import org.openrdf.repository.sail.SailRepository; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFHandlerException; import org.openrdf.rio.RDFWriter; import org.openrdf.rio.RDFWriterFactory; import org.openrdf.rio.RDFWriterRegistry; import org.openrdf.rio.Rio; import edu.kit.aifb.cumulus.AbstractCumulusWebTest; import edu.kit.aifb.cumulus.StubServletOutputStream; import edu.kit.aifb.cumulus.framework.Environment.ConfigParams; import edu.kit.aifb.cumulus.framework.Environment.ConfigValues; import edu.kit.aifb.cumulus.store.CumulusStoreException; import edu.kit.aifb.cumulus.store.sesame.CumulusRDFSail; import edu.kit.aifb.cumulus.util.Util; import edu.kit.aifb.cumulus.webapp.HttpProtocol.Headers; import edu.kit.aifb.cumulus.webapp.HttpProtocol.Methods; import edu.kit.aifb.cumulus.webapp.HttpProtocol.MimeTypes; import edu.kit.aifb.cumulus.webapp.HttpProtocol.Parameters; import edu.kit.aifb.cumulus.webapp.writer.HTMLWriter; public class LinkedDataServletTest extends AbstractCumulusWebTest { private HttpServletRequest _request; private HttpServletResponse _response; private LinkedDataServlet _ld_servlet; private static List<String> URIS, URIS_INVALID, TRIPLES_NT, TRIPLES_INVALID_NT, TRIPLES_INVALID_XML; static { URIS = Arrays.asList("http://gridpedia.org/id/Actor"); URIS_INVALID = Arrays.asList("http://gridpedia.org/id/Actor2"); TRIPLES_INVALID_XML = Arrays.asList("<?xml version=\"1.0\" encoding=\"utf-8\"?> <rdf:RDF x"); TRIPLES_NT = Arrays .asList("<http://gridpedia.org/id/Actor> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .\n " + "<http://gridpedia.org/id/Actor> <http://semantic-mediawiki.org/swivt/1.0#page> <http://gridpedia.org/wiki/Actor> .\n <http://gridpedia.org/id/Actor>" + " <http://semantic-mediawiki.org/swivt/1.0#wikiPageModificationDate> \"2012-02-01T09:53:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .\n " + "<http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#comment> \"Actors have the capability to make decisions and exchange " + "information with other actors through interfaces. Actors my be devices, computer systems, or software programs and/or the organizations that own them. " + "An actor may also comprise other actors. Source: NIST Framework and Roadmap for Smart Grid Interoperability Standards, National Institute of Standards" + " and Technology (2010).\"^^<http://www.w3.org/2001/XMLSchema#string> .\n <http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#comment> " + "\"Netznutzer: nat\u00FCrliche oder juristische Personen, die Energie in ein Elektrizit\u00E4ts- oder Gasversorgungsnetz einspeisen oder daraus beziehen " + "(http://bundesrecht.juris.de/enwg_2005/__3.html). Quelle: MeRegioMobil http://meregiomobil.forschung.kit.edu/.\"^^<http://www.w3.org/2001/XMLSchema#string> .\n" + " <http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#comment> \"Ein Agent ist eine (nat\u00FCrliche oder juristische) Person," + " welche eine Transaktion auf dem Markt im Auftrag seines Kunden oder seines Arbeitgebers ausf\u00FChrt. Es kann erforderlich sein, dass f\u00FCr gewisse " + "Transaktionen ein Personenbezug hergestellt werden kann. Regelungen des Wertpapierhandelsgesetztes sind gegebenenfalls zu ber\u00FCcksichtigen, da der " + "Marktplatz b\u00F6rsen\u00E4hnlich aufgebaut ist.Quelle: MeRegioMobil http://meregiomobil.forschung.kit.edu/.\"^^<http://www.w3.org/2001/XMLSchema#string> .\n" + " <http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#isDefinedBy> <http://gridpedia.org/wiki/Actor> .\n <http://gridpedia.org/id/Actor> " + "<http://www.w3.org/2000/01/rdf-schema#label> \"Actor\"^^<http://www.w3.org/2001/XMLSchema#string> .\n <http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#seeAlso> " + "<http://dbpedia.org/resource/Actant> .\n <http://gridpedia.org/id/Actor> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://gridpedia.org/id/Thing> .\n"); TRIPLES_INVALID_NT = Arrays.asList("<http://gridpedia.org/id/Actor> <http://www.w3.org/1999/02/22-rd"); /* * ensure that our Sesame HTMLWriter is registered */ RDFFormat.register(HTML_FORMAT); RDFWriterRegistry.getInstance().add(new RDFWriterFactory() { @Override public RDFFormat getRDFFormat() { return HTML_FORMAT; } @Override public RDFWriter getWriter(OutputStream out) { return new HTMLWriter(out); } @Override public RDFWriter getWriter(Writer writer) { return new HTMLWriter(writer); } }); } @BeforeClass public static void setUp() throws Exception { TRIPLE_STORE = newTripleStore(); SAIL = new CumulusRDFSail(TRIPLE_STORE); REPOSITORY = new SailRepository(SAIL); REPOSITORY.initialize(); } /** * Tear down fixture for each test. * Basically deletes all data in the store. * * @throws Exception hopefully never, otherwise the corresponding test fails. */ @After public void afterEachTest() throws Exception { clean(TRIPLE_STORE); } @Before public void beforeEachTest() throws Exception { ServletContext context = mock(ServletContext.class); when(context.getAttribute(ConfigParams.SESAME_REPO)).thenReturn(REPOSITORY); when(context.getAttribute(ConfigParams.STORE)).thenReturn(TRIPLE_STORE); when(context.getAttribute(ConfigParams.LAYOUT)).thenReturn(ConfigValues.STORE_LAYOUT_TRIPLE); _ld_servlet = spy(new LinkedDataServlet()); _ld_servlet.init(); doReturn(context).when(_ld_servlet).getServletContext(); when(context.getNamedDispatcher(anyString())).thenReturn(mock(RequestDispatcher.class)); _request = mock(HttpServletRequest.class); when(_request.getRequestDispatcher(anyString())).thenReturn(mock(RequestDispatcher.class)); when(_request.getHeader(Headers.CONTENT_TYPE)).thenReturn(MimeTypes.N_TRIPLES); when(_request.getMethod()).thenReturn(Methods.POST); _response = mock(HttpServletResponse.class); TRIPLE_STORE.bulkLoad(DATA_NT, RDFFormat.NTRIPLES); for (int i = 0; i < TRIPLES_NT.size(); i++) { final String triples = TRIPLES_NT.get(i); when(_request.getInputStream()).thenReturn(new ServletInputStream() { private InputStream _delegate = new ByteArrayInputStream(triples.getBytes()); @Override public int read() throws IOException { return _delegate.read(); } }); _ld_servlet.doPost(_request, _response); } } @Test public void delete() throws IOException, CumulusStoreException, ServletException { for (int i = 0; i < URIS.size(); i++) { /* * prepare mock ... */ String uri = URIS.get(i); when(_request.getRequestURL()).thenReturn(new StringBuffer(uri)); /* * delete entity */ _ld_servlet.doDelete(_request, _response); /* * verify deletion */ verify(_response).setStatus(HttpServletResponse.SC_OK); assertTrue("entity '" + uri + "' should have been deleted", !TRIPLE_STORE.query(new Value[] { ValueFactoryImpl.getInstance().createURI(uri), null, null }) .hasNext()); } } @Test public void deleteInvalid() throws IOException, ServletException { for (int i = 0; i < URIS_INVALID.size(); i++) { /* * prepare mock ... */ when(_request.getRequestURL()).thenReturn(new StringBuffer(URIS_INVALID.get(i))); /* * delete entity */ _ld_servlet.doDelete(_request, _response); /* * verify deletion */ verify(_response).setStatus(HttpServletResponse.SC_NOT_FOUND); } } @Test public void get() throws IOException, Exception { for (String mime_type : MimeTypes.RDF_SERIALIZATIONS) { when(_request.getHeader(Parameters.ACCEPT)).thenReturn(mime_type); for (int i = 0; i < URIS.size(); i++) { /* * prepare mock ... */ File tmp = tmpFile(); when(_response.getOutputStream()).thenReturn(new StubServletOutputStream(tmp)); when(_request.getRequestURL()).thenReturn(new StringBuffer(URIS.get(i))); /* * get data */ _ld_servlet.doGet(_request, _response); /* * verify ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_OK); assertTrue(parseAsList(new FileInputStream(tmp), RDFFormat.forMIMEType(mime_type)).size() > 0); } } } @Test public void getHTML() throws IOException, Exception { when(_request.getHeader(Parameters.ACCEPT)).thenReturn(MimeTypes.TEXT_HTML); for (int i = 0; i < URIS.size(); i++) { /* * prepare mock ... */ when(_response.getOutputStream()).thenReturn(new StubServletOutputStream(tmpFile())); when(_request.getRequestURL()).thenReturn(new StringBuffer(URIS.get(i))); /* * get data */ _ld_servlet.doGet(_request, _response); /* * verify ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_OK); } } @Test public void getInvalid() throws IOException, ServletException { for (String mime_type : MimeTypes.RDF_SERIALIZATIONS) { when(_request.getHeader(Parameters.ACCEPT)).thenReturn(mime_type); for (int i = 0; i < URIS_INVALID.size(); i++) { /* * prepare mock ... */ when(_request.getRequestURL()).thenReturn(new StringBuffer(URIS_INVALID.get(i))); /* * get data */ _ld_servlet.doGet(_request, _response); /* * verify ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_NOT_FOUND); } } } @Test public void post() throws IOException, CumulusStoreException, RDFHandlerException, ServletException { for (int i = 0; i < TRIPLES_NT.size(); i++) { for (String mime_type : MimeTypes.RDF_SERIALIZATIONS) { /* * clear data ... */ TRIPLE_STORE.clear(); assertTrue("store should be empty", !TRIPLE_STORE.query(new Value[] { null, null, null }).hasNext()); /* * prepare data in desired RDF serialization */ Model model = new LinkedHashModel(parseNX(TRIPLES_NT.get(i))); final ByteArrayOutputStream out = new ByteArrayOutputStream(); Rio.write(model, out, RDFFormat.forMIMEType(mime_type)); /* * prepare mock ... */ when(_request.getHeader(Headers.CONTENT_TYPE)).thenReturn(mime_type); when(_request.getInputStream()).thenReturn(new ServletInputStream() { final InputStream _inputStream = new ByteArrayInputStream(out.toByteArray()); @Override public int read() throws IOException { return _inputStream.read(); } }); /* * HTTP POST */ _ld_servlet.doPost(_request, _response); /* * verify the HTTP POST ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_CREATED); for (Statement stmt : model) { assertTrue("statement '" + stmt + "' has not been been added correctly for serialization '" + mime_type + "'", TRIPLE_STORE.query(Util.toValueArray(stmt)) .hasNext()); } } } } @Test public void postInvalidNT() throws IOException, ServletException { when(_request.getHeader(Parameters.CONTENT_TYPE)).thenReturn(MimeTypes.N_TRIPLES); for (int i = 0; i < TRIPLES_INVALID_NT.size(); i++) { /* * prepare mock ... */ final String triples = TRIPLES_INVALID_NT.get(i); when(_request.getInputStream()).thenReturn(new ServletInputStream() { private InputStream _delegate = new ByteArrayInputStream(triples.getBytes()); @Override public int read() throws IOException { return _delegate.read(); } }); /* * HTTP POST ... */ _ld_servlet.doPost(_request, _response); /* * verify the HTTP POST ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_BAD_REQUEST); } } @Test public void postInvalidXML() throws IOException, URISyntaxException, ServletException { when(_request.getHeader(Parameters.CONTENT_TYPE)).thenReturn(MimeTypes.RDF_XML); for (int i = 0; i < TRIPLES_INVALID_XML.size(); i++) { /* * prepare mock ... */ final String triples = TRIPLES_INVALID_XML.get(i); when(_request.getInputStream()).thenReturn(new ServletInputStream() { private InputStream _delegate = new ByteArrayInputStream(triples.getBytes()); @Override public int read() throws IOException { return _delegate.read(); } }); /* * HTTP POST ... */ _ld_servlet.doPost(_request, _response); /* * verify the HTTP POST ... */ verify(_response, atLeastOnce()).setStatus(HttpServletResponse.SC_BAD_REQUEST); } } }