sourceafBedSheet::BedSheetModule.fan

using web
using afIoc
using afIocEnv
using concurrent::Actor
using afPlastic::PlasticCompiler
using afIocConfig::FactoryDefaults
using afIocConfig::ConfigProvider
using afIocConfig::IocConfigSource
using afIocConfig::IocConfigModule

** The [Ioc]`http://www.fantomfactory.org/pods/afIoc` module class.
** 
** This class is public so it may be referenced explicitly in test code.
@SubModule { modules=[IocConfigModule#, IocEnvModule#] }
const class BedSheetModule {
    // IocConfigModule is referenced explicitly so there is no dicking about with transitive 
    // dependencies on BedSheet startup
    
    static Void bind(ServiceBinder binder) {
        
        // Utils
        binder.bind(PipelineBuilder#)
        binder.bind(StackFrameFilter#)

        // Request handlers
        binder.bind(FileHandler#)
        binder.bind(PodHandler#)

        // Collections (services with contributions)
        binder.bind(ResponseProcessors#)
        binder.bind(ErrProcessors#)
        binder.bind(HttpStatusProcessors#) 
        binder.bind(Routes#)
        binder.bind(ValueEncoders#)
        
        // Other services
        binder.bind(GzipCompressible#)
        binder.bind(NotFoundPrinterHtml#)
        binder.bind(ErrPrinterHtml#)
        binder.bind(ErrPrinterStr#)
        binder.bind(HttpSession#)
        binder.bind(HttpCookies#)
        binder.bind(HttpFlash#).withScope(ServiceScope.perThread)   // Because HttpFlash is thread scope, it needs a proxy to be injected into AppScope services
        binder.bind(BedSheetPages#)
    }

    @Build { serviceId="BedSheetMetaData" }
    static BedSheetMetaData buildBedSheetMetaData(RegistryOptions options) {
        if (!options.options.containsKey("bedSheetMetaData"))
            throw BedSheetErr(BsErrMsgs.bedSheetMetaDataNotInOptions)
        return options.options["bedSheetMetaData"] 
    }

    // No need for a proxy, you don't advice the pipeline, you contribute to it!
    // App scope 'cos the pipeline has no state - the pipeline is welded / hardcoded together!
    @Build { serviceId="MiddlewarePipeline"; disableProxy=true }
    static MiddlewarePipeline buildMiddlewarePipeline(Middleware[] userMiddleware, PipelineBuilder bob, Registry reg) {
        // hardcode BedSheet default middleware
        middleware := Middleware[
            reg.autobuild(CleanupMiddleware#),
            reg.autobuild(ErrMiddleware#),
            reg.autobuild(FlashMiddleware#),
            reg.autobuild(RequestLogMiddleware#)
        ].addAll(userMiddleware)
        terminator := reg.autobuild(MiddlewareTerminator#)
        return bob.build(MiddlewarePipeline#, Middleware#, middleware, terminator)
    }

    @Build { serviceId="HttpRequest" }
    static HttpRequest buildHttpRequest(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(HttpRequestImpl#))
    }

    @Build { serviceId="HttpResponse" }
    static HttpResponse buildHttpResponse(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(HttpResponseImpl#))
    }

    @Build { serviceId="HttpOutStream"; disableProxy=true; scope=ServiceScope.perThread }
    static OutStream buildHttpOutStream(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(WebResOutProxy#))
    }

    @Build { serviceId="WebReq"; scope=ServiceScope.perThread } 
    private static WebReq buildWebReq() {
        try return Actor.locals["web.req"]
        catch (NullErr e) 
            throw Err("No web request active in thread")
    }

    @Build { serviceId="WebRes"; scope=ServiceScope.perThread } 
    private static WebRes buildWebRes() {
        try return Actor.locals["web.res"]
        catch (NullErr e)
            throw Err("No web request active in thread")
    }

    @Contribute { serviceType=MiddlewarePipeline# }
    static Void contributeMiddlewarePipeline(OrderedConfig conf) {
        conf.addOrdered("Routes", conf.autobuild(RoutesMiddleware#))
    }

    @Contribute { serviceId="HttpOutStream" }
    static Void contributeHttpOutStream(OrderedConfig conf) {
        conf.addOrdered("HttpOutStreamBuffBuilder",     conf.autobuild(HttpOutStreamBuffBuilder#), ["before: HttpOutStreamGzipBuilder"])
        conf.addOrdered("HttpOutStreamGzipBuilder",     conf.autobuild(HttpOutStreamGzipBuilder#))
    }

    @Contribute { serviceType=ResponseProcessors# }
    static Void contributeResponseProcessors(MappedConfig conf, HttpStatusProcessors httpStatusProcessor) {
        conf[Text#]             = conf.autobuild(TextResponseProcessor#)
        conf[File#]             = conf.autobuild(FileResponseProcessor#)
        conf[Redirect#]         = conf.autobuild(RedirectResponseProcessor#)
        conf[InStream#]         = conf.autobuild(InStreamResponseProcessor#)
        conf[MethodCall#]       = conf.autobuild(MethodCallResponseProcessor#)
        conf[HttpStatus#]       = httpStatusProcessor
    }

    @Contribute { serviceType=ValueEncoders# }
    static Void contributeValueEncoders(MappedConfig config) {
        // wot no value encoders!? Aha! I see you're using fromStr() instead!
    }

    @Contribute { serviceType=Routes# }
    static Void contributeFileHandlerRoutes(OrderedConfig conf, FileHandler fileHandler) {
        conf.addPlaceholder("FileHandlerStart")
        fileHandler.directoryMappings.each |dir, uri| {
            conf.add(Route(uri + `***`, FileHandler#service))
        }
        conf.addPlaceholder("FileHandlerEnd")
    }
    
    @Contribute { serviceType=GzipCompressible# }
    static Void contributeGzipCompressible(MappedConfig conf) {
        // add some standard compressible mime types
        conf["text/css"]                    = true
        conf["text/html"]                   = true
        conf["text/javascript"]             = true
        conf["text/plain"]                  = true
        conf["text/tab-separated-values"]   = true
        conf["text/xml"]                    = true
        conf["application/xhtml+xml"]       = true
        conf["application/rss+xml"]         = true
        conf["application/json"]            = true

        // compress web fonts
        // see http://stackoverflow.com/questions/2871655/proper-mime-type-for-fonts#20723357
        conf["application/vnd.ms-fontobject "]  = true  // eot
        conf["application/font-sfnt"]           = true  // ttf, otf
        conf["image/svg+xml"]                   = true  // svg
        conf["application/font-woff"]           = false // woff files are already gzip compressed
    }

    @Contribute { serviceType=NotFoundPrinterHtml# }
    static Void contributeNotFoundPrinterHtml(OrderedConfig config) {
        printer := (NotFoundPrinterHtmlSections) config.autobuild(NotFoundPrinterHtmlSections#)

        // these are all the sections you see on the 404 page
        config.addOrdered("RouteCode",              |WebOutStream out| { printer.printRouteCode         (out) })
        config.addOrdered("BedSheetRoutes",         |WebOutStream out| { printer.printBedSheetRoutes    (out) })
    }

    @Contribute { serviceType=ErrPrinterHtml# }
    static Void contributeErrPrinterHtml(OrderedConfig config) {
        printer := (ErrPrinterHtmlSections) config.autobuild(ErrPrinterHtmlSections#)

        // these are all the sections you see on the Err500 page
        config.addOrdered("Causes",                 |WebOutStream out, Err? err| { printer.printCauses                  (out, err) })
        config.addOrdered("AvailableValues",        |WebOutStream out, Err? err| { printer.printAvailableValues         (out, err) })
        config.addOrdered("IocOperationTrace",      |WebOutStream out, Err? err| { printer.printIocOperationTrace       (out, err) })
        config.addOrdered("SrcCodeErrs",            |WebOutStream out, Err? err| { printer.printSrcCodeErrs             (out, err) })
        config.addOrdered("StackTrace",             |WebOutStream out, Err? err| { printer.printStackTrace              (out, err) })
        config.addOrdered("RequestDetails",         |WebOutStream out, Err? err| { printer.printRequestDetails          (out, err) })
        config.addOrdered("RequestHeaders",         |WebOutStream out, Err? err| { printer.printRequestHeaders          (out, err) })
        config.addOrdered("FormParameters",         |WebOutStream out, Err? err| { printer.printFormParameters          (out, err) })
        config.addOrdered("Session",                |WebOutStream out, Err? err| { printer.printSession                 (out, err) })
        config.addOrdered("Cookies",                |WebOutStream out, Err? err| { printer.printCookies                 (out, err) })
        config.addOrdered("Locales",                |WebOutStream out, Err? err| { printer.printLocales                 (out, err) })
        config.addOrdered("IocConfig",              |WebOutStream out, Err? err| { printer.printIocConfig               (out, err) })
        config.addOrdered("Routes",                 |WebOutStream out, Err? err| { printer.printBedSheetRoutes          (out, err) })
        config.addOrdered("Locals",                 |WebOutStream out, Err? err| { printer.printLocals                  (out, err) })
        config.addOrdered("FantomEnvironment",      |WebOutStream out, Err? err| { printer.printFantomEnvironment       (out, err) })
        config.addOrdered("FantomPods",             |WebOutStream out, Err? err| { printer.printFantomPods              (out, err) })
        config.addOrdered("FantomIndexedProps",     |WebOutStream out, Err? err| { printer.printFantomIndexedProps      (out, err) })
        config.addOrdered("EnvironmentVariables",   |WebOutStream out, Err? err| { printer.printEnvironmentVariables    (out, err) })
        config.addOrdered("FantomDiagnostics",      |WebOutStream out, Err? err| { printer.printFantomDiagnostics       (out, err) })
    }

    @Contribute { serviceType=ErrPrinterStr# }
    static Void contributeErrPrinterStr(OrderedConfig config) {
        printer := (ErrPrinterStrSections) config.autobuild(ErrPrinterStrSections#)
        
        // these are all the sections you see on the Err log
        config.addOrdered("Causes",                 |StrBuf out, Err? err| { printer.printCauses            (out, err) })
        config.addOrdered("AvailableValues",        |StrBuf out, Err? err| { printer.printAvailableValues   (out, err) })
        config.addOrdered("IocOperationTrace",      |StrBuf out, Err? err| { printer.printIocOperationTrace (out, err) })
        config.addOrdered("SrcCodeErrs",            |StrBuf out, Err? err| { printer.printSrcCodeErrs       (out, err) })       
        config.addOrdered("StackTrace",             |StrBuf out, Err? err| { printer.printStackTrace        (out, err) })
        config.addOrdered("RequestDetails",         |StrBuf out, Err? err| { printer.printRequestDetails    (out, err) })
        config.addOrdered("RequestHeaders",         |StrBuf out, Err? err| { printer.printRequestHeaders    (out, err) })
        config.addOrdered("FormParameters",         |StrBuf out, Err? err| { printer.printFormParameters    (out, err) })
        config.addOrdered("Session",                |StrBuf out, Err? err| { printer.printSession           (out, err) })
        config.addOrdered("Cookies",                |StrBuf out, Err? err| { printer.printCookies           (out, err) })
        config.addOrdered("Locales",                |StrBuf out, Err? err| { printer.printLocales           (out, err) })
        config.addOrdered("IocConfig",              |StrBuf out, Err? err| { printer.printIocConfig         (out, err) })
        config.addOrdered("Routes",                 |StrBuf out, Err? err| { printer.printRoutes            (out, err) })
        config.addOrdered("Locals",                 |StrBuf out, Err? err| { printer.printLocals            (out, err) })
    }
    
    @Contribute { serviceType=FactoryDefaults# }
    static Void contributeFactoryDefaults(MappedConfig conf, Registry reg, IocEnv iocEnv, BedSheetMetaData meta) {
        conf[BedSheetConfigIds.proxyPingInterval]           = 1sec
        conf[BedSheetConfigIds.gzipDisabled]                = false
        conf[BedSheetConfigIds.gzipThreshold]               = 376
        conf[BedSheetConfigIds.responseBufferThreshold]     = 32 * 1024 // todo: why not kB?
        conf[BedSheetConfigIds.defaultHttpStatusProcessor]  = reg.createProxy(DefaultHttpStatusProcessor#)
        conf[BedSheetConfigIds.defaultErrProcessor]         = reg.createProxy(DefaultErrProcessor#)
        conf[BedSheetConfigIds.noOfStackFrames]             = 50
        conf[BedSheetConfigIds.srcCodeErrPadding]           = 5
        conf[BedSheetConfigIds.disableWelcomePage]          = false
        conf[BedSheetConfigIds.host]                        = `http://localhost:${meta.port}`

        conf[BedSheetConfigIds.requestLogDir]               = null
        conf[BedSheetConfigIds.requestLogFilenamePattern]   = "bedSheet-{YYYY-MM}.log"
        conf[BedSheetConfigIds.requestLogFields]            = "date time c-ip cs(X-Real-IP) cs-method cs-uri-stem cs-uri-query sc-status time-taken cs(User-Agent) cs(Referer) cs(Cookie)"
    }
    
    @Contribute { serviceType=StackFrameFilter# }
    static Void contributeStackFrameFilter(OrderedConfig config) {
        config.add("concurrent::Actor._dispatch")
        config.add("concurrent::Actor._send")
        config.add("concurrent::Actor._work")
        config.add("concurrent::ThreadPool\$Worker.run")
        config.add("fan.sys.Method\$MethodFunc.callOn")
        config.add("fan.sys.Func\$Indirect0.call")
        config.add("java.lang.reflect.Method.invoke")
    }
    
    @Contribute { serviceType=RegistryStartup# }
    static Void contributeRegistryStartup(OrderedConfig conf, PlasticCompiler plasticCompiler, IocConfigSource configSrc, BedSheetMetaData meta) {
        conf.add |->| {
            plasticCompiler.srcCodePadding = configSrc.get(BedSheetConfigIds.srcCodeErrPadding, Int#)
        }
    }
    
    private static Obj makeDelegateChain(DelegateChainBuilder[] delegateBuilders, Obj service) {
        delegateBuilders.reduce(service) |Obj delegate, DelegateChainBuilder builder -> Obj| {      
            return builder.build(delegate)
        }
    }
}