All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jena.fuseki.embedded.FusekiServer Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.fuseki.embedded;

import java.util.ArrayList;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;

import javax.servlet.ServletContext ;
import javax.servlet.http.HttpServlet;

import org.apache.jena.atlas.lib.Pair;
import org.apache.jena.fuseki.Fuseki ;
import org.apache.jena.fuseki.FusekiConfigException ;
import org.apache.jena.fuseki.FusekiException ;
import org.apache.jena.fuseki.build.FusekiBuilder ;
import org.apache.jena.fuseki.build.FusekiConfig ;
import org.apache.jena.fuseki.jetty.FusekiErrorHandler1 ;
import org.apache.jena.fuseki.mgt.ActionStats ;
import org.apache.jena.fuseki.server.DataAccessPoint ;
import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
import org.apache.jena.fuseki.server.DataService ;
import org.apache.jena.fuseki.servlets.FusekiFilter ;
import org.apache.jena.query.Dataset ;
import org.apache.jena.riot.WebContent;
import org.apache.jena.sparql.core.DatasetGraph ;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.HttpConnectionFactory ;
import org.eclipse.jetty.server.Server ;
import org.eclipse.jetty.server.ServerConnector ;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder ;
import org.eclipse.jetty.servlet.ServletContextHandler ;
import org.eclipse.jetty.servlet.ServletHolder;

/**
 * Embedded Fuseki server. This is a Fuseki server running with a preconfigured set of
 * datasets and services. There is no admin UI and no security.
 * 

* To create a embedded sever, use {@link FusekiServer} ({@link #make} is a * packaging of a call to {@link FusekiServer} for the case of one dataset, * responding to localhost only). *

* The application should call {@link #start()} to actually start the server * (it wil run in the background : see {@link #join}). *

Example: *

 *      DatasetGraph dsg = ... ;
 *      FusekiServer server = FusekiServer.create()
 *          .setPort(1234)
 *          .add("/ds", dsg)
 *          .build() ;
 *       server.start() ;
 * 
* Compact form (use the builder pattern above to get more flexibility): *
 *    FusekiServer.make(1234, "/ds", dsg).start() ;
 * 
* */ public class FusekiServer { static { //FusekiEnv.mode = FusekiEnv.INIT.EMBEDDED ; // Stop anything accidently resetting Fuseki server logging. //FusekiLogging.allowLoggingReset(false) ; } /** Construct a Fuseki server for one dataset. * It only responds to localhost. * The returned server has not been started */ static public FusekiServer make(int port, String name, DatasetGraph dsg) { return create() .setPort(port) .setLoopback(true) .add(name, dsg) .build() ; } public static Builder create() { return new Builder() ; } public final Server server ; private int port ; private FusekiServer(int port, Server server) { this.server = server ; // This should be the same. //this.port = ((ServerConnector)server.getConnectors()[0]).getPort() ; this.port = port ; } /** * Return the port begin used. * This will be the give port, which defaults to 3330, or * the one actually allocated if the port was 0 ("choose a free port"). */ public int getPort() { return port ; } /** Get the underlying Jetty server which has also been set up. */ public Server getJettyServer() { return server ; } /** Get the {@link ServletContext}. * Adding new servlets is possible with care. */ public ServletContext getServletContext() { return ((ServletContextHandler)server.getHandler()).getServletContext() ; } /** Get the {@link DataAccessPointRegistry}. * This method is intended for inspecting the registry. */ public DataAccessPointRegistry getDataAccessPointRegistry() { return DataAccessPointRegistry.get(getServletContext()) ; } /** Start the server - the server continues to run after this call returns. * To synchronise with the server stopping, call {@link #join}. */ public FusekiServer start() { try { server.start(); } catch (Exception e) { throw new FusekiException(e) ; } if ( port == 0 ) port = ((ServerConnector)server.getConnectors()[0]).getLocalPort() ; Fuseki.serverLog.info("Start Fuseki (port="+port+")"); return this ; } /** Stop the server. */ public void stop() { Fuseki.serverLog.info("Stop Fuseki (port="+port+")"); try { server.stop(); } catch (Exception e) { throw new FusekiException(e) ; } } /** Wait for the server to exit. This call is blocking. */ public void join() { try { server.join(); } catch (Exception e) { throw new FusekiException(e) ; } } /** FusekiServer.Builder */ public static class Builder { private Map map = new HashMap<>() ; // Default values. private int port = 3330 ; private boolean loopback = false ; private boolean withStats = false ; // Other servlets to add. private List> other = new ArrayList<>(); private String contextPath = "/" ; private String staticContentDir = null ; private SecurityHandler securityHandler = null ; /** Set the port to run on. */ public Builder setPort(int port) { if ( port < 0 ) throw new IllegalArgumentException("Illegal port="+port+" : Port must be greater than or equal to zero.") ; this.port = port ; return this ; } /** Context path to Fuseki. If it's "/" then Fuseki URL look like * "http://host:port/dataset/query" else "http://host:port/path/dataset/query" */ public Builder setContextPath(String path) { this.contextPath = path ; return this ; } /** Restrict the server to only responding to the localhost interface. */ public Builder setLoopback(boolean loopback) { this.loopback = loopback; return this ; } /** Set the location (filing system directory) to serve static file from. */ public Builder setStaticFileBase(String directory) { this.staticContentDir = directory; return this ; } /** Set a Jetty SecurityHandler. * By default, the server runs with no security. * This is more for using the basic server for testing. * The full Fuseki server provides secjurity with Apache Shiro * and a defensive reverse proxy (e.g. Apache httpd) in front og the Jetty server * can also be used, which provides a wide varient of proven security options. */ public Builder setSecurityHandler(SecurityHandler securityHandler) { this.securityHandler = securityHandler; return this; } /** Add the "/$/stats" servlet that responds with stats about the server, * including counts of all calls made. */ public Builder enableStats(boolean withStats) { this.withStats = withStats; return this ; } /** Add the dataset with given name and a default set of services including update */ public Builder add(String name, Dataset ds) { return add(name, ds.asDatasetGraph()) ; } /** Add the dataset with given name and a default set of services including update */ public Builder add(String name, DatasetGraph dsg) { return add(name, dsg, true) ; } /** Add the dataset with given name and a default set of services. */ public Builder add(String name, Dataset ds, boolean allowUpdate) { return add(name, ds.asDatasetGraph(), allowUpdate) ; } /** Add the dataset with given name and a default set of services. */ public Builder add(String name, DatasetGraph dsg, boolean allowUpdate) { DataService dSrv = FusekiBuilder.buildDataService(dsg, allowUpdate) ; return add(name, dSrv) ; } /** Add a data service that includes dataset and service names. * A {@link DataService} allows for choices of the various endpoint names. */ public Builder add(String name, DataService dataService) { return add$(name, dataService) ; } private Builder add$(String name, DataService dataService) { name = DataAccessPoint.canonical(name) ; if ( map.containsKey(name) ) throw new FusekiConfigException("Data service name already registered: "+name) ; map.put(name, dataService) ; return this ; } /** Read and parse a Fuseki services/datasets file. *

* The application is responsible for ensuring a correct classpath. For example, * including a dependency on {@code jena-text} if the configuration file * includes a text index. */ public Builder parseConfigFile(String filename) { List x = FusekiConfig.readConfigurationFile(filename) ; // Unbundle so that they accumulate. x.forEach(dap-> add(dap.getName(), dap.getDataService())) ; return this ; } /** * Add the given servlet with the pathSpec. These are added so that they are * checked after the Fuseki filter for datasets and before the static content * handler (which is the last servlet) used for {@link #setStaticFileBase(String)}. */ public Builder addServlet(String pathSpec, HttpServlet servlet) { other.add(Pair.create(pathSpec, servlet)); return this; } /** Build a server according to the current description */ public FusekiServer build() { DataAccessPointRegistry registry = new DataAccessPointRegistry() ; map.forEach((name, dSrv) -> { DataAccessPoint dap = new DataAccessPoint(name, dSrv) ; registry.put(name, dap) ; }) ; ServletContextHandler handler = buildServletContext(contextPath, registry) ; setMimeTypes(handler); servlets(handler); DataAccessPointRegistry.set(handler.getServletContext(), registry) ; Server server = jettyServer(port, loopback) ; server.setHandler(handler); return new FusekiServer(port, server) ; } /** Build a ServletContextHandler with the Fuseki router : {@link FusekiFilter} */ private ServletContextHandler buildServletContext(String contextPath, DataAccessPointRegistry registry) { if ( contextPath == null || contextPath.isEmpty() ) contextPath = "/" ; else if ( !contextPath.startsWith("/") ) contextPath = "/" + contextPath ; ServletContextHandler context = new ServletContextHandler() ; context.setDisplayName(Fuseki.servletRequestLogName) ; context.setErrorHandler(new FusekiErrorHandler1()) ; context.setContextPath(contextPath) ; if ( securityHandler != null ) context.setSecurityHandler(securityHandler); return context ; } private static void setMimeTypes(ServletContextHandler context) { MimeTypes mimeTypes = new MimeTypes(); // RDF syntax mimeTypes.addMimeMapping("nt", WebContent.contentTypeNTriples); mimeTypes.addMimeMapping("nq", WebContent.contentTypeNQuads); mimeTypes.addMimeMapping("ttl", WebContent.contentTypeTurtle+";charset=utf-8"); mimeTypes.addMimeMapping("trig", WebContent.contentTypeTriG+";charset=utf-8"); mimeTypes.addMimeMapping("rdfxml", WebContent.contentTypeRDFXML); mimeTypes.addMimeMapping("jsonld", WebContent.contentTypeJSONLD); mimeTypes.addMimeMapping("rj", WebContent.contentTypeRDFJSON); mimeTypes.addMimeMapping("rt", WebContent.contentTypeRDFThrift); mimeTypes.addMimeMapping("trdf", WebContent.contentTypeRDFThrift); // SPARQL syntax mimeTypes.addMimeMapping("rq", WebContent.contentTypeSPARQLQuery); mimeTypes.addMimeMapping("ru", WebContent.contentTypeSPARQLUpdate); // SPARQL Result set mimeTypes.addMimeMapping("rsj", WebContent.contentTypeResultsJSON); mimeTypes.addMimeMapping("rsx", WebContent.contentTypeResultsXML); mimeTypes.addMimeMapping("srt", WebContent.contentTypeResultsThrift); // Other mimeTypes.addMimeMapping("txt", WebContent.contentTypeTextPlain); mimeTypes.addMimeMapping("csv", WebContent.contentTypeTextCSV); mimeTypes.addMimeMapping("tsv", WebContent.contentTypeTextTSV); context.setMimeTypes(mimeTypes); } private void servlets(ServletContextHandler context) { // Fuseki dataset services filter FusekiFilter ff = new FusekiFilter() ; FilterHolder h = new FilterHolder(ff) ; context.addFilter(h, "/*", null) ; other.forEach(p->addServlet(context, p.getLeft(), p.getRight())); if ( withStats ) addServlet(context, "/$/stats", new ActionStats()) ; if ( staticContentDir != null ) { DefaultServlet staticServlet = new DefaultServlet() ; ServletHolder staticContent = new ServletHolder(staticServlet) ; staticContent.setInitParameter("resourceBase", staticContentDir) ; context.addServlet(staticContent, "/"); } } private static void addServlet(ServletContextHandler context, String pathspec, HttpServlet httpServlet) { ServletHolder sh = new ServletHolder(httpServlet); context.addServlet(sh, pathspec) ; } /** Jetty server */ private static Server jettyServer(int port, boolean loopback) { Server server = new Server() ; HttpConnectionFactory f1 = new HttpConnectionFactory() ; // Some people do try very large operations ... really, should use POST. f1.getHttpConfiguration().setRequestHeaderSize(512 * 1024); f1.getHttpConfiguration().setOutputBufferSize(1024 * 1024) ; // Do not add "Server: Jetty(....) when not a development system. if ( ! Fuseki.outputJettyServerHeader ) f1.getHttpConfiguration().setSendServerVersion(false) ; ServerConnector connector = new ServerConnector(server, f1) ; connector.setPort(port) ; server.addConnector(connector); if ( loopback ) connector.setHost("localhost"); return server ; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy