package edu.kit.aifb.cumulus.webapp; import java.io.IOException; import java.util.Iterator; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.ValueFactory; import org.openrdf.model.impl.ValueFactoryImpl; import org.openrdf.repository.RepositoryResult; import org.openrdf.repository.sail.SailRepository; import org.openrdf.repository.sail.SailRepositoryConnection; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFHandler; import org.openrdf.rio.RDFHandlerException; import org.openrdf.rio.RDFParseException; import org.openrdf.rio.RDFParser; import org.openrdf.rio.RDFWriter; import org.openrdf.rio.Rio; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterators; import edu.kit.aifb.cumulus.framework.Environment; import edu.kit.aifb.cumulus.framework.Environment.ConfigParams; import edu.kit.aifb.cumulus.log.Log; import edu.kit.aifb.cumulus.log.MessageCatalog; import edu.kit.aifb.cumulus.store.CumulusStoreException; import edu.kit.aifb.cumulus.store.Store; import static edu.kit.aifb.cumulus.webapp.HttpProtocol.*; import static edu.kit.aifb.cumulus.util.Util.toResultIterator; /** * * Servlet that allows read/write access for Linked Data resources. * Note, content negotiation is used for determining the input/output RDF serialization. * * @see <a href="http://www.w3.org/DesignIssues/LinkedData.html">http://www.w3.org/DesignIssues/LinkedData.html</a> * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html">http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html</a> * * @author Steffen Stadtmueller * @author Andreas Wagner * @since 1.0 * */ public class LinkedDataServlet extends AbstractCumulusServlet { private static final long serialVersionUID = 1L; private Log _log = new Log(LoggerFactory.getLogger(getClass())); private ValueFactory _valueFactory; /** * <p> Deletes all triples associated with the specific entity. That is, an HTTP DELETE request * on the URI {@code http://example.org/resource/1} would delete the triples:</p> * <br> * <p> * http://http://example.org/resource/1 ?p ?o . <br> * ?s ?p http://http://example.org/resource/1 . * </p> * */ @Override public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { final Store store = ((Store) getServletContext().getAttribute(ConfigParams.STORE)); if ((store == null) || !store.isOpen()) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG + " Store was null or not initialized."); sendError( req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG); return; } final Iterator<Statement> removeIterator = store.describe(ValueFactoryImpl.getInstance().createURI(req.getRequestURL().toString()), false); if (removeIterator.hasNext()) { store.removeData(removeIterator); resp.setStatus(HttpServletResponse.SC_OK); } else { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); } } catch (Exception exception) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG, exception); } } /** * <p> Retrieves all triples associated with the specific entity. That is, an HTTP GET request * on the URI {@code http://example.org/resource/1} would retrieve the triples:</p> * <br> * <p> * http://http://example.org/resource/1 ?p ?o . <br> * ?s ?p http://http://example.org/resource/1 . * </p> * <br> * <p>HTTP header 'Accept' is used to determine the MIME type for the RDF serialization.</p> * */ @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final RDFFormat format = Rio.getWriterFormatForMIMEType(parseAcceptHeader(req)); if (format == null) { _log.debug(MessageCatalog._00115_WEB_MODULE_REQUEST_NOT_VALID + " Mime-type '" + parseAcceptHeader(req) + "' not supported."); sendError(req, resp, HttpServletResponse.SC_BAD_REQUEST, MessageCatalog._00115_WEB_MODULE_REQUEST_NOT_VALID + " Mime-type '" + parseAcceptHeader(req) + "' not supported."); return; } SailRepositoryConnection connection = null; RepositoryResult<Statement> repo_res1 = null, repo_res2 = null; try { SailRepository repository = (SailRepository) getServletContext().getAttribute(ConfigParams.SESAME_REPO); if ((repository == null) || !repository.isInitialized()) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG + " Repository was null or not initialized."); sendError( req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG); return; } connection = repository.getConnection(); URI entity = _valueFactory.createURI(req.getRequestURL().toString()); final Iterator<Statement> res1 = toResultIterator(repo_res1 = connection.getStatements(entity, null, null, false)); final Iterator<Statement> res2 = toResultIterator(repo_res2 = connection.getStatements(null, null, entity, false)); if (!res1.hasNext() && !res2.hasNext()) { sendError(req, resp, HttpServletResponse.SC_NOT_FOUND, MessageCatalog._00033_RESOURCE_NOT_FOUND_MSG); } else { resp.setCharacterEncoding(Environment.CHARSET_UTF8.name()); resp.setContentType(format.getDefaultMIMEType()); resp.setStatus(HttpServletResponse.SC_OK); RDFWriter writer = Rio.createWriter(format, resp.getOutputStream()); Rio.write(new Iterable<Statement>() { @Override public Iterator<Statement> iterator() { return Iterators.concat(res1, res2); } }, writer); } } catch (Exception exception) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG, exception); } finally { try { if (repo_res1 != null) { repo_res1.close(); } if (repo_res2 != null) { repo_res2.close(); } if (connection != null) { connection.close(); } } catch (Exception exception) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG, exception); } } } /** * <p> Posts triples associated with the specific entity.</p> * <p> HTTP header 'Content-Type' is used to determine the MIME type for the RDF serialization.</p> */ @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { RDFFormat format = Rio.getWriterFormatForMIMEType(parseContentTypeHeader(req)); if (format == null) { _log.debug(MessageCatalog._00115_WEB_MODULE_REQUEST_NOT_VALID + " Content-type '" + parseContentTypeHeader(req) + "' not supported."); sendError(req, resp, HttpServletResponse.SC_BAD_REQUEST, MessageCatalog._00115_WEB_MODULE_REQUEST_NOT_VALID + " Content '" + parseContentTypeHeader(req) + "' not supported."); return; } try { final Store crdf = ((Store) getServletContext().getAttribute(ConfigParams.STORE)); if ((crdf == null) || !crdf.isOpen()) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG + " Store was null or not initialized."); sendError( req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG); return; } RDFParser parser = Rio.createParser(format); parser.setRDFHandler(new RDFHandler() { @Override public void endRDF() throws RDFHandlerException { } @Override public void handleComment(String comment) throws RDFHandlerException { } @Override public void handleNamespace(String prefix, String uri) throws RDFHandlerException { } @Override public void handleStatement(Statement st) throws RDFHandlerException { try { crdf.addData(st); } catch (CumulusStoreException e) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE, e); } } @Override public void startRDF() throws RDFHandlerException { } }); /* * this allows proper handling of RuntimeExceptions ... */ try { parser.parse(req.getInputStream(), parseBaseURI(req)); } catch (RuntimeException exception) { throw new RDFParseException(exception); } resp.setStatus(HttpServletResponse.SC_CREATED); } catch (RDFParseException exception) { _log.debug(MessageCatalog._00029_RDF_PARSE_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_BAD_REQUEST, MessageCatalog._00029_RDF_PARSE_FAILURE, exception); } catch (RDFHandlerException exception) { _log.debug(MessageCatalog._00029_RDF_PARSE_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_BAD_REQUEST, MessageCatalog._00029_RDF_PARSE_FAILURE, exception); } catch (IOException exception) { _log.debug(MessageCatalog._00029_RDF_PARSE_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_BAD_REQUEST, MessageCatalog._00029_RDF_PARSE_FAILURE, exception); } catch (Exception exception) { _log.error(MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE, exception); sendError(req, resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageCatalog._00025_CUMULUS_SYSTEM_INTERNAL_FAILURE_MSG, exception); } } /** * Request is forwarded to {@link LinkedDataServlet#doPost(HttpServletRequest, HttpServletResponse)}. */ @Override public void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override public void init() throws ServletException { _valueFactory = ValueFactoryImpl.getInstance(); } }