blob: abfc1426e4465201f5859d18b75b6438a657bcd3 [file] [log] [blame]
/*
* 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.nlpcraft.server
import com.typesafe.config.Config
import com.typesafe.scalalogging.LazyLogging
import org.apache.commons.lang3.SystemUtils
import org.apache.nlpcraft.common._
import org.apache.nlpcraft.common.ansi.NCAnsi
import org.apache.nlpcraft.common.ascii.NCAsciiTable
import org.apache.nlpcraft.common.config.NCConfigurable
import org.apache.nlpcraft.common.extcfg.NCExternalConfigManager
import org.apache.nlpcraft.common.module.NCModule
import org.apache.nlpcraft.common.nlp.core.NCNlpCoreManager
import org.apache.nlpcraft.common.nlp.dict.NCDictionaryManager
import org.apache.nlpcraft.common.nlp.numeric.NCNumericManager
import org.apache.nlpcraft.common.opencensus.NCOpenCensusTrace
import org.apache.nlpcraft.common.pool.NCThreadPoolManager
import org.apache.nlpcraft.common.version._
import org.apache.nlpcraft.model.tools.cmdline.NCCliServerBeacon
import org.apache.nlpcraft.server.company.NCCompanyManager
import org.apache.nlpcraft.server.feedback.NCFeedbackManager
import org.apache.nlpcraft.server.geo.NCGeoManager
import org.apache.nlpcraft.server.ignite.{NCIgniteInstance, NCIgniteRunner}
import org.apache.nlpcraft.server.lifecycle.NCServerLifecycleManager
import org.apache.nlpcraft.server.nlp.core.NCNlpServerManager
import org.apache.nlpcraft.server.nlp.enrichers.NCServerEnrichmentManager
import org.apache.nlpcraft.server.nlp.preproc.NCPreProcessManager
import org.apache.nlpcraft.server.nlp.spell.NCSpellCheckManager
import org.apache.nlpcraft.server.nlp.wordnet.NCWordNetManager
import org.apache.nlpcraft.server.probe.NCProbeManager
import org.apache.nlpcraft.server.proclog.NCProcessLogManager
import org.apache.nlpcraft.server.query.NCQueryManager
import org.apache.nlpcraft.server.rest.NCRestManager
import org.apache.nlpcraft.server.sql.NCSqlManager
import org.apache.nlpcraft.server.sugsyn.NCSuggestSynonymManager
import org.apache.nlpcraft.server.tx.NCTxManager
import org.apache.nlpcraft.server.user.NCUserManager
import resource.managed
import java.io._
import java.util.concurrent.CountDownLatch
import scala.collection.mutable
import scala.compat.Platform.currentTime
import scala.util.control.Exception.{catching, ignoring}
/**
* NLPCraft server app.
*/
object NCServer extends App with NCIgniteInstance with LazyLogging with NCOpenCensusTrace {
private final val BEACON_PATH = ".nlpcraft/server_beacon"
private val startedMgrs = mutable.Buffer.empty[NCService]
/**
* Prints ASCII-logo.
*/
private def asciiLogo() {
val ver = NCVersion.getCurrent
logger.info(
U.NL +
U.asciiLogo() +
s"${U.NL}" +
s"Server${U.NL}" +
s"Version: ${bo(ver.version)}${U.NL}" +
s"${NCVersion.copyright}${U.NL}"
)
}
/**
* Starts all managers.
*/
private def startManagers(): Unit = {
val ver = NCVersion.getCurrent
// Lifecycle manager has to be started outside of the tracing span.
NCServerLifecycleManager.start()
NCServerLifecycleManager.beforeStart()
startScopedSpan("startManagers", "relVer" → ver.version, "relDate" → ver.date) { span ⇒
startedMgrs += NCThreadPoolManager.start(span)
startedMgrs += NCExternalConfigManager.start(span)
startedMgrs += NCWordNetManager.start(span)
startedMgrs += NCDictionaryManager.start(span)
startedMgrs += NCTxManager.start(span)
startedMgrs += NCSqlManager.start(span)
startedMgrs += NCProcessLogManager.start(span)
startedMgrs += NCGeoManager.start(span)
startedMgrs += NCNlpCoreManager.start(span)
startedMgrs += NCNlpServerManager.start(span)
startedMgrs += NCNumericManager.start(span)
startedMgrs += NCSpellCheckManager.start(span)
startedMgrs += NCPreProcessManager.start(span)
startedMgrs += NCServerEnrichmentManager.start(span)
startedMgrs += NCUserManager.start(span)
startedMgrs += NCCompanyManager.start(span)
startedMgrs += NCProbeManager.start(span)
startedMgrs += NCSuggestSynonymManager.start(span)
startedMgrs += NCFeedbackManager.start(span)
startedMgrs += NCQueryManager.start(span)
startedMgrs += NCRestManager.start(span)
// Lifecycle callback.
NCServerLifecycleManager.afterStart()
}
}
/**
* Stops all managers.
*/
private def stopManagers(): Unit = {
// Lifecycle callback.
NCServerLifecycleManager.beforeStop()
startScopedSpan("stopManagers") { span ⇒
startedMgrs.reverse.foreach(p ⇒
try
p.stop(span)
catch {
case e: Exception ⇒ U.prettyError(logger, s"Error stopping manager: ${p.name}", e)
}
)
}
// Lifecycle callback.
NCServerLifecycleManager.afterStop()
NCServerLifecycleManager.stop()
}
/**
* Acks server start.
*/
protected def ackStart() {
val dur = s"[${U.format((currentTime - executionStart) / 1000.0, 2)}s]"
val tbl = NCAsciiTable()
tbl.margin(top = 1, bottom = 1)
tbl += s"Server started ${b(dur)}"
tbl.info(logger)
}
/**
*
* @return
*/
private def setSysProps(): Unit = {
System.setProperty("java.net.preferIPv4Stack", "true")
NCModule.setModule(NCModule.SERVER)
}
/**
*
*/
private def start(): Unit = {
NCAnsi.ackStatus()
setSysProps()
NCConfigurable.initialize(
None, // No overrides.
args.find(_.startsWith("-config=")) match {
case Some(s)
val fileName = s.substring("-config=".length)
val f = new java.io.File(fileName)
if (!(f.exists && f.canRead && f.isFile))
throw new NCE(s"Specified server configuration file does not exist or cannot be read: $fileName")
Some(fileName)
case None
Some("nlpcraft.conf") // Default to 'nlpcraft.conf'.
},
None, // No defaults.
(cfg: Config) ⇒ cfg.hasPath("nlpcraft.server")
)
asciiLogo()
val lifecycle = new CountDownLatch(1)
catching(classOf[Throwable]) either startManagers() match {
case Left(e)// Exception.
U.prettyError(logger, "Failed to start server:", e)
stopManagers()
System.exit(1)
case _ ⇒ // Managers started OK.
// Store beacon file once all managers started OK.
storeBeacon()
ackStart()
Runtime.getRuntime.addShutdownHook(new Thread() {
override def run(): Unit = {
ignoring(classOf[Throwable]) {
stopManagers()
}
lifecycle.countDown()
}
})
U.ignoreInterrupt {
lifecycle.await()
}
}
}
/**
*
*/
private def storeBeacon(): Unit = {
val path = new File(SystemUtils.getUserHome, BEACON_PATH)
/**
*
*/
def save(): Unit = {
final object Config extends NCConfigurable {
final private val pre = "nlpcraft.server"
lazy val pid = ProcessHandle.current().pid()
lazy val restHost = getString(s"$pre.rest.host")
lazy val restApi = getString(s"$pre.rest.apiImpl")
lazy val restPort = getInt(s"$pre.rest.port")
lazy val upLink = getString(s"$pre.probe.links.upLink")
lazy val downLink = getString(s"$pre.probe.links.downLink")
lazy val dbUrl = getString(s"$pre.database.jdbc.url")
lazy val dbDriver = getString(s"$pre.database.jdbc.driver")
lazy val dbPoolMin = getInt(s"$pre.database.c3p0.pool.minSize")
lazy val dbPoolMax = getInt(s"$pre.database.c3p0.pool.maxSize")
lazy val dbPoolInit = getInt(s"$pre.database.c3p0.pool.initSize")
lazy val dbPoolInc = getInt(s"$pre.database.c3p0.pool.acquireIncrement")
lazy val dbInit = getBool(s"$pre.database.igniteDbInitialize")
lazy val tokProviders = getString(s"$pre.tokenProviders")
lazy val acsToksScanMins = getInt(s"$pre.user.timeoutScannerFreqMins")
lazy val acsToksExpireMins = getInt(s"$pre.user.accessTokenExpireTimeoutMins")
lazy val nlpEngine = getString("nlpcraft.nlpEngine")
lazy val extCfgUrl = getString("nlpcraft.extConfig.extUrl")
lazy val extCfgCheckMd5 = getBool("nlpcraft.extConfig.checkMd5")
lazy val restEndpoint = s"${Config.restHost}:${Config.restPort}/api/v1"
}
try {
managed(new ObjectOutputStream(new FileOutputStream(path))) acquireAndGet { stream ⇒
stream.writeObject(NCCliServerBeacon(
pid = Config.pid,
dbUrl = Config.dbUrl,
dbDriver = Config.dbDriver,
dbPoolMin = Config.dbPoolMin,
dbPoolMax = Config.dbPoolMax,
dbPoolInit = Config.dbPoolInit,
dbPoolInc = Config.dbPoolInc,
dbInit = Config.dbInit,
restEndpoint = Config.restEndpoint,
restApi = Config.restApi,
upLink = Config.upLink,
downLink = Config.downLink,
tokenProviders = Config.tokProviders,
nlpEngine = Config.nlpEngine,
extConfigUrl = Config.extCfgUrl,
extConfigCheckMd5 = Config.extCfgCheckMd5,
acsToksScanMins = Config.acsToksScanMins,
acsToksExpireMins = Config.acsToksExpireMins,
beaconPath = path.getAbsolutePath,
startMs = currentTime
))
stream.flush()
}
// Make sure beacon is deleted when server process exits.
path.deleteOnExit()
val tbl = NCAsciiTable()
tbl += (s"${b("PID")}", Config.pid)
tbl += (s"${b("Database:")}", "")
tbl += (s"${b(" JDBC URL")}", Config.dbUrl)
tbl += (s"${b(" JDBC Driver")}", Config.dbDriver)
tbl += (s"${b(" Pool min")}", Config.dbPoolMin)
tbl += (s"${b(" Pool init")}", Config.dbPoolInit)
tbl += (s"${b(" Pool max")}", Config.dbPoolMax)
tbl += (s"${b(" Pool increment")}", Config.dbPoolInc)
tbl += (s"${b(" Reset on start")}", Config.dbInit)
tbl += (s"${b("REST:")}", "")
tbl += (s"${b(" Endpoint")}", Config.restEndpoint)
tbl += (s"${b(" API provider")}", Config.restApi)
tbl += (s"${b("Probe:")}", "")
tbl += (s"${b(" Uplink")}", Config.upLink)
tbl += (s"${b(" Downlink")}", Config.downLink)
tbl += (s"${b("Token providers")}", Config.tokProviders)
tbl += (s"${b("NLP engine")}", Config.nlpEngine)
tbl += (s"${b("Access tokens:")}", "")
tbl += (s"${b(" Scan frequency")}", s"${Config.acsToksScanMins} mins")
tbl += (s"${b(" Expiration timeout")}", s"${Config.acsToksExpireMins} mins")
tbl += (s"${b("External config:")}", "")
tbl += (s"${b(" URL")}", Config.extCfgUrl)
tbl += (s"${b(" Check MD5")}", Config.extCfgCheckMd5)
logger.info(s"Sever configuration:\n$tbl")
}
catch {
case e: IOException ⇒ U.prettyError(logger, "Failed to save server beacon.", e)
}
}
if (path.exists())
catching(classOf[IOException]) either {
managed(new ObjectInputStream(new FileInputStream(path))) acquireAndGet {
_.readObject()
}
} match {
case Left(e)
logger.trace(s"Failed to read existing server beacon: ${path.getAbsolutePath}", e)
logger.trace(s"Overriding corrupted server beacon: ${path.getAbsolutePath}")
save()
case Right(rawObj)
val beacon = rawObj.asInstanceOf[NCCliServerBeacon]
if (ProcessHandle.of(beacon.pid).isPresent) {
logger.warn(s"Another local server detected (safely ignored) [pid=${beacon.pid}]")
logger.warn(s"NOTE: ${c("only one")} locally deployed server can be managed by 'nlpcraft.{sh|cmd}' management script.")
// Leave the existing server beacon as is...
} else {
logger.trace(s"Overriding server beacon for a phantom process [pid=${beacon.pid}]")
save()
}
}
else
// No existing beacon file detected.
save()
}
// Screen view.
new Thread() {
override def run(): Unit = U.gaScreenView("server")
}
.start()
NCIgniteRunner.runWith(
args.find(_.startsWith("-igniteConfig=")) match {
case Nonenull // Will use default on the classpath 'ignite.xml'.
case Some(s) ⇒ s.substring(s.indexOf("=") + 1)
},
start()
)
}