using afIoc
using afIocEnv
using inet::IpAddr
** Use to programmatically create and launch BedSheet server instances.
**
** syntax: fantom
** useDevProxy := true
** BedSheetBuilder(AppModule#).startWisp(8069, useDevProxy, "dev")
**
class BedSheetBuilder {
private const static Log log := Utils.getLog(BedSheetBuilder#)
private IpAddr? ipAddr
** The application name. Taken from the app pod's 'proj.name' meta, or the pod name if the meta doesn't exist.
** Read only.
Str appName {
get { options[BsConstants.meta_appName] }
private set { throw Err("Read only") }
}
** The HTTP port to run the app on. Defaults to '8069'
Int port {
get { registryBuilder.options[BsConstants.meta_appPort] }
set { registryBuilder.options[BsConstants.meta_appPort] = it }
}
** Returns the options from the IoC 'RegistryBuilder'.
Str:Obj? options {
get { registryBuilder.options }
private set { throw Err("Read only") }
}
** The underlying IoC 'RegistryBuilder'.
** Read only.
RegistryBuilder registryBuilder { private set }
private new makeFromBob(RegistryBuilder bob) {
this.registryBuilder = bob
initBanner(registryBuilder)
}
** Creates a 'BedSheetBuilder'.
** 'appName' may be a pod name or a qualified 'AppModule' type name.
** 'addPodDependencies' is only used if a pod name is passed in.
new make(Str appName, Bool addPodDependencies := true) {
this.registryBuilder = RegistryBuilder()
this.port = 0
initModules(registryBuilder, appName, addPodDependencies)
initBanner(registryBuilder)
}
** Creates a 'BedSheetBuilder' from an 'AppModule' type.
new makeFromType(Type appModule) : this.make(appModule.qname) { }
** Sets a value in the 'options' map.
** Returns 'this' so it may be used as a builder method.
This setOption(Str name, Obj? value) {
registryBuilder.set(name, value)
return this
}
** Sets the local IP address that Wisp should bind to, or set to 'null' for the default.
**
** This is useful when deploying your application to [Open Shift]`https://developers.openshift.com/en/diy-overview.html`
** or similar where the local IP address is mandated.
** See the Fantom Forum topic: [IP address for afBedSheet]`http://fantom.org/forum/topic/2399`.
This setIpAddress(IpAddr? ipAddr) {
this.ipAddr = ipAddr
return this
}
** Adds a module to the registry.
** Any modules defined with the '@SubModule' facet are also added.
**
** Convenience for 'registryBuilder.addModule()'
This addModule(Type moduleType) {
registryBuilder.addModule(moduleType)
return this
}
** Adds many modules to the registry
**
** Convenience for 'registryBuilder.addModules()'
This addModules(Type[] moduleTypes) {
registryBuilder.addModules(moduleTypes)
return this
}
** Inspects the [pod's meta-data]`docLang::Pods#meta` for the key 'afIoc.module'. This is then
** treated as a CSV list of (qualified) module type names to load.
**
** If 'addDependencies' is 'true' then the pod's dependencies are also inspected for IoC
** modules.
**
** Convenience for 'registryBuilder.addModulesFromPod()'
This addModulesFromPod(Str podName, Bool addDependencies := true) {
registryBuilder.addModulesFromPod(podName, addDependencies)
return this
}
@NoDoc @Deprecated { msg="Use 'build()' instead" }
Registry buildRegistry() { build }
** Builds the IoC 'Registry'.
** Note that this does **NOT** call 'startup()' on the registry.
Registry build() {
registryBuilder.removeModule(IocEnvModule#)
return registryBuilder.build
}
** Convenience method to start a Wisp server running BedSheet.
Int startWisp(Int port := 8069, Bool proxy := false, Str? env := null) {
this.port = port
options["afBedSheet.env"] = env
watchAllPods := options[BsConstants.meta_watchAllPods]?.toStr?.toBool(false) ?: false
mod := proxy ? ProxyMod(this, port, watchAllPods) : BedSheetBootMod(this)
return WebModRunner().run(mod, port, ipAddr)
}
@NoDoc // for serialisation
Str toStringy() {
bob := registryBuilder.dup
bob.options.remove("afIoc.bannerText")
// Pod's aren't serializable
appPod := (Pod) bob.options[BsConstants.meta_appPod]
bob.options[BsConstants.meta_appPodName] = appPod.name
bob.options.remove(BsConstants.meta_appPod)
// from a std Fantom-Factory builder:
// - raw str = 763 bytes
// - base64 = 985 bytes
// - gzipped = 421 bytes
buf := Buf()
Zip.gzipOutStream(buf.out).writeObj(bob).close
return buf.flip.toBase64.replace("/", "_").replace("+", "-")
}
@NoDoc // for serialisation
static BedSheetBuilder fromStringy(Str str) {
b64 := str.replace("_", "/").replace("-", "+")
bob := (RegistryBuilder) Zip.gzipInStream(Buf.fromBase64(b64).in).readObj
// reinstate appPod
appPodName := (Str) bob.options[BsConstants.meta_appPodName]
bob.options[BsConstants.meta_appPod] = Pod.find(appPodName, true)
bob.options.remove(BsConstants.meta_appPodName)
return BedSheetBuilder(bob)
}
private static Void initBanner(RegistryBuilder bob) {
bannerText := easterEgg("Alien-Factory BedSheet v${BedSheetWebMod#.pod.version}, IoC v${Registry#.pod.version}")
bob.options["afIoc.bannerText"] = bannerText
}
private static Void initModules(RegistryBuilder bob, Str moduleName, Bool transDeps) {
Pod? pod
Type? mod
Type[] mods := Type#.emptyList
// 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))
mods = findModFromPod(pod)
mod = mods.first
}
// 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)
if (pod != null) {
if (!transDeps)
log.info("Suppressing transitive dependencies...")
bob.addModulesFromPod(pod.name, transDeps)
}
if (mod != null) {
if (!bob.moduleTypes.contains(mod))
bob.addModule(mod)
}
bob.addModules(mods)
// A simple thing - ensure the BedSheet module is added!
// (transitive dependencies are added explicitly via @SubModule)
if (!bob.moduleTypes.contains(BedSheetModule#))
bob.addModule(BedSheetModule#)
regOpts := bob.options
regOpts[BsConstants.meta_appName] = (pod?.meta?.get("proj.name") ?: pod?.name) ?: "Unknown"
regOpts[BsConstants.meta_appPod] = pod
regOpts[BsConstants.meta_appModule] = mod
}
** Looks for an 'AppModule' in the given pod.
private static Type[] findModFromPod(Pod pod) {
mods := Type#.emptyList
modNames := pod.meta["afIoc.module"]
if (modNames != null) {
mods = modNames.split.map { Type.find(it, true) }
log.info(BsLogMsgs.bedSheetWebMod_foundType(mods.first))
} else {
// we have a pod with no module meta... so lets guess the name 'AppModule'
mod := pod.type("AppModule", false)
if (mod != null) {
mods = [mod]
log.info(BsLogMsgs.bedSheetWebMod_foundType(mod))
log.warn(BsLogMsgs.bedSheetWebMod_addModuleToPodMeta(pod, mod))
}
}
return mods
}
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)]
}
private static Str[] loadQuotes() {
BedSheetWebMod#.pod.file(`/res/misc/quotes.txt`).readAllLines.exclude { it.isEmpty || it.startsWith("#")}
}
}