using concurrent::Actor
using concurrent::ActorPool
using concurrent::AtomicRef
using concurrent::AtomicBool
using web::WebMod
using afIoc::IocErr
using afIoc::IocShutdownErr
using afIoc::Registry
using afIoc::RegistryBuilder
using afIocConfig::ConfigSource
** The `web::WebMod` to be passed to [Wisp]`http://fantom.org/doc/wisp/index.html`.
const class BedSheetWebMod : WebMod {
private const static Log log := Utils.getLog(BedSheetWebMod#)
** The module name passed into the ctor.
** Can be either a qualified type name of an AppModule or a pod name.
const Str moduleName
** The port number this Bed App will be listening on.
const Int port
@NoDoc // advanced usage
const [Str:Obj?] registryOptions
private const AtomicBool started := AtomicBool(false)
private const AtomicRef startupErrA := AtomicRef()
private const AtomicRef registryRef := AtomicRef()
private const AtomicRef pipelineRef := AtomicRef()
private const AtomicRef errPrinterRef := AtomicRef()
** The 'afIoc' registry. Can be 'null' if BedSheet has not started.
Registry? registry {
get { registryRef.val }
private set { registryRef.val = it }
}
** The Err (if any) that occurred on service startup
Err? startupErr {
get { startupErrA.val }
private set { startupErrA.val = it }
}
** Creates this 'WebMod'.
** 'moduleName' can be a qualified type name of an AppModule or a pod name.
new make(Str moduleName, Int port, [Str:Obj?]? registryOptions := null) {
this.moduleName = moduleName
this.port = port
this.registryOptions = registryOptions ?: Utils.makeMap(Str#, Obj?#)
}
@NoDoc
override Void onService() {
req.mod = this
// Hey! Why you call us when we're not running, eh!??
if (!started.val)
return
// web reqs can come while we're still processing onStart() so lets wait for either
// condition to occur (good or bad) - as some reg startup times may be seconds long
while (registry == null && startupErr == null) {
// 200ms should be un-noticable to humans but a lifetime to a computer!
Actor.sleep(200ms)
}
// rethrow the startup err if one occurred and let Wisp handle it
if (startupErr != null)
throw startupErr
try {
middlewarePipeline.service
} catch (IocShutdownErr err) {
// nothing we can do here
return
} catch (Err err) {
// theoretically, this should have already been dealt with by our ErrMiddleware...
// ...but it's handy for BedSheet development!
if (registry != null) { // reqs may come in before we've start up
try {
errPrinter := (ErrPrinterStr) registry.serviceById(ErrPrinterStr#.qname)
Env.cur.err.printLine(errPrinter.errToStr(err))
} catch
err.trace(Env.cur.err)
}
throw err
}
}
@NoDoc
override Void onStart() {
started.val = true
try {
log.info(BsLogMsgs.bedSheetWebMod_starting(moduleName, port))
bob := createBob(moduleName, port, registryOptions)
// Go!!!
registry = bob.build.startup
// start the destroyer!
if (registryOptions["afBedSheet.pingProxy"] == true) {
pingPort := (Int) registryOptions["afBedSheet.pingProxyPort"]
destroyer := (AppDestroyer) registry.autobuild(AppDestroyer#, [ActorPool(), pingPort])
destroyer.start
}
// print BedSheet connection details
configSrc := (ConfigSource) registry.dependencyByType(ConfigSource#)
host := (Uri) configSrc.get(BedSheetConfigIds.host, Uri#)
log.info(BsLogMsgs.bedSheetWebMod_started(bob["afBedSheet.appName"], host))
} catch (Err err) {
startupErr = err
throw err
}
}
@NoDoc
override Void onStop() {
registry?.shutdown
log.info(BsLogMsgs.bedSheetWebMod_stopping(moduleName))
}
** Returns a fully loaded IoC 'RegistryBuilder' that creates everything this Bed App needs.
static RegistryBuilder createBob(Str moduleName, Int port, [Str:Obj?]? options := null) {
options = options ?: Utils.makeMap(Str#, Obj?#)
Pod? pod
Type? mod
// Pod name given...
// lots of start up checks looking for pods and modules...
// see https://bitbucket.org/SlimerDude/afbedsheet/issue/1/add-a-warning-when-no-appmodule-is-passed
if (!moduleName.contains("::")) {
pod = Pod.find(moduleName, true)
log.info(BsLogMsgs.bedSheetWebMod_foundPod(pod))
mod = findModFromPod(pod)
}
// AppModule name given...
if (moduleName.contains("::")) {
mod = Type.find(moduleName, true)
log.info(BsLogMsgs.bedSheetWebMod_foundType(mod))
pod = mod.pod
}
// we're screwed! No module = no web app!
if (mod == null)
log.warn(BsLogMsgs.bedSheetWebMod_noModuleFound)
// construct after the above messages so logs look nicer ("...adding module IocModule")
bob := RegistryBuilder()
// this defaults to false if not explicitly set to TRUE - trust me!
transDeps := !(options["afBedSheet.noTransDeps"] == true)
if (pod != null) {
if (transDeps)
bob.addModulesFromPod(pod, true)
else
log.info("Suppressing transitive dependencies...")
}
if (mod != null) {
if (!bob.moduleTypes.contains(mod))
bob.addModule(mod)
}
// A simple thing - ensure the BedSheet module is added!
// (transitive dependencies are added explicitly via @SubModule)
if (!bob.moduleTypes.contains(BedSheetModule#))
bob.addModule(BedSheetModule#)
// add extra modules - useful for testing
if (options.containsKey("afBedSheet.iocModules"))
bob.addModules(options["afBedSheet.iocModules"])
dPort := (options.containsKey("afBedSheet.pingProxy") ? options["afBedSheet.pingProxyPort"] : null) ?: port
regOpts := options.rw
regOpts["afIoc.bannerText"] = easterEgg("Alien-Factory BedSheet v${BedSheetWebMod#.pod.version}, IoC v${Registry#.pod.version}")
regOpts["afBedSheet.appPod"] = pod
regOpts["afBedSheet.appModule"] = mod
regOpts["afBedSheet.port"] = dPort
regOpts["afBedSheet.appName"] = pod?.meta?.get("proj.name") ?: "Unknown"
bob.options.addAll(regOpts)
// startup afIoc
return bob
}
** Looks for an 'AppModule' in the given pod.
private static Type? findModFromPod(Pod pod) {
mod := null
modName := pod.meta["afIoc.module"]
if (modName != null) {
mod = Type.find(modName, true)
log.info(BsLogMsgs.bedSheetWebMod_foundType(mod))
} else {
// we have a pod with no module meta... so lets guess the name 'AppModule'
mod = pod.type("AppModule", false)
if (mod != null) {
log.info(BsLogMsgs.bedSheetWebMod_foundType(mod))
log.warn(BsLogMsgs.bedSheetWebMod_addModuleToPodMeta(pod, mod))
}
}
return mod
}
private static Str easterEgg(Str title) {
quotes := loadQuotes
if (quotes.isEmpty || (Int.random(0..8) != 2))
return title
return quotes[Int.random(0..<quotes.size)]
}
// lazy load the MiddlewarePipeline
private MiddlewarePipeline middlewarePipeline() {
pipe := pipelineRef.val
if (pipe == null)
pipe = pipelineRef.val = registry.serviceById(MiddlewarePipeline#.qname)
return pipe
}
private static Str[] loadQuotes() {
BedSheetWebMod#.pod.file(`/res/misc/quotes.txt`).readAllLines.exclude { it.isEmpty || it.startsWith("#")}
}
}