/* * Copyright 2015 Network New Technologies Inc. * * Licensed 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 com.networknt.light.server; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import com.jayway.jsonpath.spi.mapper.MappingProvider; import com.networknt.light.rule.RuleEngine; import com.networknt.light.rule.rule.AbstractRuleRule; import com.networknt.light.server.handler.RestHandler; import com.networknt.light.server.handler.WebSocketHandler; import com.networknt.light.util.ServiceLocator; import com.networknt.light.util.Util; import com.orientechnologies.orient.core.config.OGlobalConfiguration; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.core.sql.OCommandSQL; import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph; import com.tinkerpop.blueprints.impls.orient.OrientGraph; import com.tinkerpop.blueprints.impls.orient.OrientGraphFactory; import com.tinkerpop.blueprints.impls.orient.OrientGraphNoTx; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.handlers.NameVirtualHostHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.builder.PredicatedHandlersParser; import io.undertow.server.handlers.form.EagerFormParsingHandler; import io.undertow.server.handlers.resource.FileResourceManager; import io.undertow.util.Headers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.ws.Service; import java.io.File; import java.io.InputStream; import java.util.*; import java.util.regex.Pattern; import static io.undertow.Handlers.resource; import static io.undertow.Handlers.websocket; public class LightServer { static final Logger logger = LoggerFactory.getLogger(LightServer.class); static protected boolean shutdownRequested = false; public static final Pattern WEBSOCKET_ALLOWED_ORIGIN_HEADER = Pattern.compile("^https?://(localhost|127\\.\\d+\\.\\d+\\.\\d+):\\d+$"); static Undertow server = null; public static void main(final String[] args) { logger.info("server starts"); // init JsonPath configJsonPath(); // add shutdown hook here. addDaemonShutdownHook(); start(); } static public void start() { // hosts and server configuration Map<String, Object> hostConfigMap = ServiceLocator.getInstance().getJsonMapConfig(ServiceLocator.HOST_CONFIG); Map<String, Object> serverConfigMap = ServiceLocator.getInstance().getJsonMapConfig(ServiceLocator.SERVER_CONFIG); OrientGraphFactory factory = ServiceLocator.getInstance().getFactory(); // check if database exists, if not create it and init it. if(!factory.exists()) { try { OrientBaseGraph g = new OrientGraph(ServiceLocator.getInstance().getDbUrl()); // database is auto created g.command(new OCommandSQL("alter database custom useLightweightEdges=true")).execute(); g.command(new OCommandSQL("alter database DATETIMEFORMAT yyyy-MM-dd'T'HH:mm:ss.SSS")).execute(); g.command(new OCommandSQL("alter database TIMEZONE UTC")).execute(); OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.setValue(-1); } finally { // this also closes the OrientGraph instances created by the factory // Note that OrientGraphFactory does not implement Closeable factory.close(); } InitDatabase.initDb(); // load rule compileCache here AbstractRuleRule.loadCompileCache(); // replay all the event to create database image. // TODO need to rethink replay as orientdb is used instead of Memory Image. replayEvent(); } else { // load rule compileCache here AbstractRuleRule.loadCompileCache(); } NameVirtualHostHandler virtualHostHandler = new NameVirtualHostHandler(); Iterator<String> it = hostConfigMap.keySet().iterator(); while (it.hasNext()) { String host = it.next(); Map<String, String> hostPropMap = (Map<String, String>)hostConfigMap.get(host); String base = hostPropMap.get("base"); String transferMinSize = hostPropMap.get("transferMinSize"); virtualHostHandler .addHost( host, Handlers.predicates( PredicatedHandlersParser.parse("not path-prefix('/images', '/assets', '/api') -> rewrite('/index.html')" //PredicatedHandlersParser.parse("not path-suffix['.js', '.html', '.css'] -> rewrite['/index.html']" //PredicatedHandlersParser.parse("path-prefix['/home', '/page', '/form'] -> rewrite['/index.html']" , LightServer.class.getClassLoader()), new PathHandler(resource(new FileResourceManager( new File(base), Integer .valueOf(transferMinSize)))) .addPrefixPath("/api/rs", new EagerFormParsingHandler().setNext( new RestHandler())) .addPrefixPath("/api/ws", websocket(new WebSocketHandler())) )); } String ip = (String)serverConfigMap.get("ip"); String port = (String)serverConfigMap.get("port"); server = Undertow .builder() .addHttpListener(Integer.valueOf(port), ip) .setBufferSize(1024 * 16) .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) .setHandler( Handlers.header(virtualHostHandler, Headers.SERVER_STRING, "LIGHT")) .setWorkerThreads(200).build(); server.start(); } static public void stop() { server.stop(); RuleEngine.getInstance().shutdown(); } // implement shutdown hook here. static public void shutdown() { stop(); logger.info("Cleaning up before server shutdown"); } static protected void addDaemonShutdownHook() { Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { LightServer.shutdown(); }}); } /** * This replay is to initialize database when the db is newly created. The initDatabase class will create * necessary entities in database to bootstrap the application. And then when the application starts the * first time, it will load all the events specified in this section to replay them in order to init db. * These file can be additional application db, rules, forms and data. There is another place to replay * events which is in dbAdmin/replay event. That can be used once system is up and running ready. * * This method will only be called when database is newly created. And it will check replay is true to * replay the events in the files specified in replayevent.json in config folder. * * In case replay is false in the replayevent.json, nothing additional will be replayed and the database * is clean in order to create object from admin or other section of the application and those events * can be downloaded from dbAdmin to populate replay event files. * */ static private void replayEvent() { Map<String, Object> config = ServiceLocator.getInstance().getJsonMapConfig("replayevent"); if((Boolean)config.get("replay") == true) { logger.info("Replay is true for db initialization. Loading event files..."); ClassLoader classloader = Thread.currentThread().getContextClassLoader(); List<Map<String, String>> entries = (List)config.get("event"); for(Map<String, String> entry: entries) { StringBuilder sb = new StringBuilder(); String location = entry.get("location"); InputStream is = classloader.getResourceAsStream(location); try (Scanner scanner = new Scanner(is)) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); sb.append(line).append("\n"); } scanner.close(); ObjectMapper mapper = ServiceLocator.getInstance().getMapper(); if(sb.length() > 0) { // if you want to generate the initdb.json from your dev env, then you should make this // file as empty in server resources folder and load all objects again. List<Map<String, Object>> events = mapper.readValue(sb.toString(), new TypeReference<List<HashMap<String, Object>>>() {}); // replay event one by one. for(Map<String, Object> event: events) { RuleEngine.getInstance().executeRule(Util.getEventRuleId(event), event); } } } catch (Exception e) { logger.error("Exception:", e); } logger.debug("Events are loaded from " + location); } } } static void configJsonPath() { Configuration.setDefaults(new Configuration.Defaults() { private final JsonProvider jsonProvider = new JacksonJsonProvider(); private final MappingProvider mappingProvider = new JacksonMappingProvider(); @Override public JsonProvider jsonProvider() { return jsonProvider; } @Override public MappingProvider mappingProvider() { return mappingProvider; } @Override public Set<Option> options() { return EnumSet.noneOf(Option.class); } }); } }