com.bagri.rest.BagriRestServer Maven / Gradle / Ivy
package com.bagri.rest;
import static com.bagri.core.Constants.bg_version;
import static com.bagri.core.Constants.pn_rest_auth_port;
import static com.bagri.core.Constants.pn_rest_jmx;
import static com.bagri.core.Constants.pn_rest_port;
import static com.bagri.rest.RestConstants.*;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.wadl.WadlFeature;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bagri.core.api.BagriException;
import com.bagri.core.system.Function;
import com.bagri.core.system.Module;
import com.bagri.core.system.Parameter;
import com.bagri.core.system.Schema;
import com.bagri.core.xquery.api.XQCompiler;
import com.bagri.rest.service.AccessService;
import com.bagri.rest.service.CollectionService;
import com.bagri.rest.service.DocumentService;
import com.bagri.rest.service.QueryService;
import com.bagri.rest.service.SchemaService;
import com.bagri.rest.service.SwaggerListener;
import com.bagri.rest.service.TransactionService;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
@Provider
public class BagriRestServer implements ContextResolver, Factory {
private static final transient Logger logger = LoggerFactory.getLogger(BagriRestServer.class);
private int port = 3030;
private int sport = 3443;
private boolean jmx = true;
private Server jettyServer;
private XQCompiler xqComp;
private RepositoryProvider rePro;
private Reloader reloader = new Reloader();
private Set activeSchemas = new HashSet<>();
public static void main(String[] args) throws Exception {
BagriRestServer server = new BagriRestServer();
try {
server.start();
server.jettyServer.join();
} finally {
server.stop();
}
}
public BagriRestServer() {
this.rePro = new LocalRepositoryProvider();
}
public BagriRestServer(RepositoryProvider rePro, XQCompiler xqComp, Properties props) {
this.rePro = rePro;
this.xqComp = xqComp;
this.jmx = Boolean.parseBoolean(props.getProperty(pn_rest_jmx, "true"));
this.port = Integer.parseInt(props.getProperty(pn_rest_port, "3030"));
this.sport = Integer.parseInt(props.getProperty(pn_rest_auth_port, "3443"));
}
public int getPort() {
return port;
}
public RepositoryProvider getRepositoryProvider() {
return rePro;
}
public ResourceConfig buildConfig() {
ResourceConfig config = new ResourceConfig(AccessService.class, CollectionService.class, DocumentService.class,
QueryService.class, SchemaService.class, TransactionService.class);
config.register(this);
config.register(AuthFilter.class);
config.register(JacksonFeature.class);
config.register(new AbstractBinder() {
@Override
protected void configure() {
bindFactory(BagriRestServer.this).to(RepositoryProvider.class);
}
});
config.register(WadlFeature.class);
config.registerInstances(reloader);
// adding Swagger support
config.register(ApiListingResource.class);
config.register(SwaggerSerializers.class);
return config;
}
public void reload(final String schemaName, final boolean force) {
if (force || !activeSchemas.contains(schemaName)) {
new Thread() {
@Override
public void run() {
// TODO: think about concurrency issues
ResourceConfig config = buildConfig();
activeSchemas.add(schemaName);
Set newList = new HashSet<>(activeSchemas.size());
SwaggerListener.clearFunctions();
for (String schema: activeSchemas) {
if (buildSchemaConfig(config, schema)) {
newList.add(schema);
}
}
logger.debug("reload.run; going to reload context for schemas: {}", newList);
reloader.reload(config);
// rebuild Swagger definitions
bildSwaggerConfig();
activeSchemas = newList;
// what about current clients?
// should we disconnect all of them?
}
}.start();
}
}
public void start() {
logger.debug("start.enter; Starting rest server");
jettyServer = createServer();
ResourceConfig config = buildConfig();
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
ServletContextHandler context = new ServletContextHandler(jettyServer, "/*");
context.addServlet(servlet, "/*");
bildSwaggerConfig();
try {
jettyServer.start();
//jettyServer.join();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
logger.debug("start.exit; Rest server is started");
}
public void stop() {
logger.debug("stop.enter; Stopping rest server");
try {
jettyServer.destroy(); //stop();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
logger.debug("stop.exit; Rest server is stopped");
}
@Override
public BagriRestServer getContext(Class> type) {
logger.trace("getContext; type: {}", type);
if (type.equals(BagriRestServer.class)) {
return this;
}
return null;
}
@Override
public RepositoryProvider provide() {
logger.trace("provide");
return rePro;
}
@Override
public void dispose(RepositoryProvider instance) {
logger.trace("dispose");
this.rePro = null;
}
private Server createServer() {
//String keyStorePath = "C:\\Work\\Bagri\\";
String keyStorePwd = "bagri11";
// First the Server itself
// Create a basic jetty server object without declaring the port. Since we are configuring connectors
// directly we'll be setting ports on those connectors.
Server server = new Server();
// Next the HttpConfiguration for http
// HTTP Configuration
// HttpConfiguration is a collection of configuration information appropriate for http and https. The default
// scheme for http is http
of course, as the default for secured http is https
but
// we show setting the scheme to show it can be done. The port for secured communication is also set here.
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSecureScheme("https");
http_config.setSecurePort(sport);
http_config.setOutputBufferSize(32768);
// Now define the ServerConnector for handling just http
// HTTP connector
// The first server connector we create is the one for http, passing in the http configuration we configured
// above so it can get things like the output buffer size, etc. We also set the port (8080) and configure an
// idle timeout.
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config));
http.setPort(port);
// Now configure the SslContextFactory with your keystore information
// SSL Context Factory for HTTPS and SPDY
// SSL requires a certificate so we configure a factory for ssl contents with information pointing to what
// keystore the ssl connection needs to know about. Much more configuration is available the ssl context,
// including things like choosing the particular certificate out of a keystore to be used.
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("../etc/keystore/");
sslContextFactory.setKeyStorePath(BagriRestServer.class.getResource("/keystore.jks").toExternalForm());
sslContextFactory.setKeyStorePassword(keyStorePwd);
sslContextFactory.setKeyManagerPassword(keyStorePwd);
// Now setup your HTTPS configuration.
// Note: the SecureRequestCustomizer, sets up various servlet api request attributes and certificate information to satisfy the requirements of the servlet spec.
// HTTPS Configuration
// A new HttpConfiguration object is needed for the next connector and you can pass the old one as an
// argument to effectively clone the contents. On this HttpConfiguration object we add a
// SecureRequestCustomizer which is how a new connector is able to resolve the https connection before
// handing control over to the Jetty Server.
//jettyServer.
HttpConfiguration https_config = new HttpConfiguration(http_config);
https_config.addCustomizer(new SecureRequestCustomizer());
// Now define the ServerConnector for handling SSL+http (aka https)
// HTTPS connector
// We create a second ServerConnector, passing in the http configuration we just made along with the
// previously created ssl context factory. Next we set the port and a longer idle timeout.
ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory,"http/1.1"), new HttpConnectionFactory(https_config));
https.setPort(sport);
https.setIdleTimeout(500000);
// Finally, add the connectors to the server
// Here you see the server having multiple connectors registered with it, now requests can flow into the server
// from both http and https urls to their respective ports and be processed accordingly by jetty. A simple
// handler is also registered with the server so the example has something to pass requests off to.
// Set the connectors
server.setConnectors(new Connector[] { http, https });
// Setup JMX
if (jmx) {
MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addEventListener(mbContainer);
server.addBean(mbContainer);
}
return server;
}
private boolean buildSchemaConfig(ResourceConfig config, String schemaName) {
Schema schema = rePro.getSchema(schemaName);
// get schema -> resources
int cnt = 0;
for (com.bagri.core.system.Resource res: schema.getResources()) {
// for each resource -> get module
if (res.isEnabled()) {
Module module = rePro.getModule(res.getModule());
try {
buildDynamicResources(config, res.getPath(), module);
cnt++;
} catch (BagriException ex) {
logger.error("buildSchemaConfig; error processing module: " + res.getModule(), ex);
// skip it..
}
}
}
return cnt > 0;
}
private void buildDynamicResources(ResourceConfig config, String basePath, Module module) throws BagriException {
Resource.Builder resourceBuilder = Resource.builder();
resourceBuilder.path(basePath);
// get functions for module
List functions = xqComp.getRestFunctions(module);
// now build Resource dynamically from the function list
for (Function function: functions) {
buildMethod(resourceBuilder, module, function);
SwaggerListener.addFunction(basePath, function);
}
Resource resource = resourceBuilder.build();
config.registerResources(resource);
logger.info("buildDynamicResources; registered resource: {}", resource);
}
private void buildMethod(Resource.Builder builder, Module module, Function fn) {
logger.debug("buildMethod; got fn: {}", fn.getSignature());
Map> annotations = fn.getAnnotations();
List values = annotations.get(an_path);
if (values != null) {
String subPath = values.get(0);
builder = builder.addChildResource(subPath);
}
//import module namespace tpox="http://tpox-benchmark.com/rest" at "../../etc/samples/tpox/rest_module.xq";
StringBuffer query = new StringBuffer("import module namespace ").
append(module.getPrefix()).append("=\"").append(module.getNamespace()).
append("\" at \"").append(module.getName()).append("\";\n"); // +
int offset = query.length();
//tpox:security-by-id($id)
query.append(fn.getPrefix()).append(":").append(fn.getMethod()).append("(");
StringBuffer params = new StringBuffer();
int cnt = 0;
//declare variable $id external;
for (Parameter param: fn.getParameters()) {
if (cnt > 0) {
query.append(", ");
}
query.append("$").append(param.getName());
params.append("declare variable $").append(param.getName()).append(" external;\n");
cnt++;
}
query.append(")\n");
params.append("\n");
query.insert(offset, params.toString());
String full = query.toString();
for (String method: methods) {
values = annotations.get("rest:" + method);
if (values != null) {
buildMethodHandler(builder, method, full, fn);
}
}
}
private void buildMethodHandler(Resource.Builder builder, String method, String query, Function fn) {
Map> annotations = fn.getAnnotations();
List consumes = annotations.get(an_consumes);
List produces = annotations.get(an_produces);
ResourceMethod.Builder methodBuilder = builder.addMethod(method);
List types;
if (consumes != null) {
types = new ArrayList<>(consumes.size());
for (String value: consumes) {
types.add(MediaType.valueOf(value));
}
methodBuilder = methodBuilder.consumes(types);
}
if (produces != null) {
types = new ArrayList<>(produces.size());
for (String value: produces) {
types.add(MediaType.valueOf(value));
}
methodBuilder = methodBuilder.produces(types);
}
RestRequestProcessor pro = new RestRequestProcessor(fn, query, rePro);
methodBuilder.handledBy(pro);
}
private void bildSwaggerConfig() {
BeanConfig beanConfig = new BeanConfig();
//beanConfig.setConfigId("configId: " + contextId++);
beanConfig.setTitle("Bagri REST server");
beanConfig.setDescription("goto http://bagridb.com for more info");
beanConfig.setContact("[email protected]");
beanConfig.setLicense("Apache 2.0");
beanConfig.setLicenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html");
beanConfig.setVersion(bg_version);
beanConfig.setSchemes(new String[] {"http", "https"});
// TODO: get host info somehow..
beanConfig.setHost("localhost:" + port);
beanConfig.setBasePath("/"); // /api
beanConfig.setResourcePackage("com.bagri.rest.service");
beanConfig.setPrettyPrint(true);
// force Swagger to re-scan the package mentioned above and use
// custom ReaderListener from that package
beanConfig.setScan(true);
}
private int contextId = 1;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy